@@ -65,7 +65,12 @@ def _paths():
6565def lib (name ):
6666 """Resolve `lib<name>.so` against the search path — also accepting the
6767 `<dir>/<name>/lib/` and `<dir>/lib/` layouts `compile_library` produces —
68- load it, and cache the handle by name."""
68+ load it, and cache the handle by name.
69+
70+ A leading `@` is accepted and ignored: this function only ever takes a
71+ name, and the sigil spelling travels from `CModel`'s string argument."""
72+ if name .startswith ("@" ):
73+ name = name [1 :]
6974 if name not in _LIBS :
7075 ext = {"win32" : ".dll" , "darwin" : ".dylib" }.get (sys .platform , ".so" )
7176 fname = f"lib{ name } { ext } "
@@ -155,9 +160,33 @@ def _check(st, what):
155160 raise RuntimeError (f"{ what } returned nonzero status { st } " )
156161
157162
158- def schema (lib , * , prefix = "rec" ):
159- """The library's data schema (ABI v2), as published by `<prefix>_schema`."""
163+ def _require_model (lib , model ):
164+ """A name this library does not carry is reported here, clearly.
165+
166+ `_nvar` is the witness symbol: the ABI requires it of every model however
167+ the model is instantiated — unlike `_new` (absent from builder-only
168+ models) or `_data_begin` (absent from one-knob ones). Without this check a
169+ mistyped name surfaces as a raw ctypes `undefined symbol` error several
170+ layers down."""
171+ try :
172+ getattr (lib , f"{ model } _nvar" )
173+ except AttributeError :
174+ where = getattr (lib , "_name" , "this library" )
175+ raise ValueError (
176+ f"{ where } carries no model named { model !r} "
177+ f"(it exports no { model } _nvar)"
178+ ) from None
179+
180+
181+ def schema (lib , model = None , * , prefix = "rec" ):
182+ """The library's data schema (ABI v2), as published by `<prefix>_schema`.
183+
184+ In a library carrying several models the schema is per model — name the
185+ one you want, `schema(lib, "acopf")`, exactly as in `CModel`."""
160186 import json
187+ if model is not None :
188+ _require_model (lib , model )
189+ prefix = model
161190 fn = getattr (lib , f"{ prefix } _schema" )
162191 fn .restype = _c_int
163192 fn .argtypes = [ctypes .POINTER (ctypes .c_uint8 ), _c_int ]
@@ -338,6 +367,16 @@ class CModel:
338367 m = cnlpmodels.CModel("rosen", 1000) # ./rosen (file or bundle dir)
339368 m = cnlpmodels.CModel("/opt/models/rosen", 1000) # full path
340369
370+ One library may carry **several models**, each under its own symbol
371+ prefix with its own schema and instances. A leading string argument
372+ selects one by name — the name is the prefix, mirroring CNLPModels.jl's
373+ `CNLPModel(lib, :acopf, ...)`; unambiguous, since a model argument is
374+ never a string. A mistyped name is refused at selection, not as a raw
375+ `undefined symbol` several calls later:
376+
377+ m = cnlpmodels.CModel("@grid", "acopf", bus, 100.0) # acopf_* in libgrid.so
378+ d = cnlpmodels.CModel("@grid", "dcopf", bus) # dcopf_*, same file
379+
341380 The arguments are the values the model is instantiated with — one per field
342381 of the library's schema, positionally, in the order the library publishes
343382 them, which is the same spelling the producer side uses
@@ -354,10 +393,26 @@ class CModel:
354393 """
355394
356395 def __init__ (self , lib , * args , prefix = None ):
396+ # A leading string argument names a MODEL in a library carrying
397+ # several — the name is the symbol prefix its ABI functions are
398+ # exported under, mirroring CNLPModels.jl's
399+ # `CNLPModel(lib, :acopf, ...)`. Unambiguous: a model argument is
400+ # never a string.
401+ model = None
402+ if args and isinstance (args [0 ], str ):
403+ model , args = args [0 ], args [1 :]
404+ if prefix is not None and prefix != model :
405+ raise TypeError (
406+ f"both a model name ({ model !r} ) and prefix= ({ prefix !r} ) "
407+ "were given; they mean the same thing — give one"
408+ )
409+ prefix = model
357410 if isinstance (lib , str ):
358411 prefix = prefix if prefix is not None else _default_prefix (lib )
359412 lib = _resolve_spec (lib )
360413 prefix = prefix if prefix is not None else "rec"
414+ if model is not None :
415+ _require_model (lib , model )
361416 # Instantiate before resolving the evaluation table, so a failure to
362417 # build the model surfaces as what it is — not as a missing evaluation
363418 # symbol on a library that never got that far. Same order as the Julia
0 commit comments