API¶
Building a model¶
- class examodels.Core(*args, cache=None, **kwargs)[source]¶
Accumulates a model, mirroring the backend’s ExaCore.
core = Core() x = core.add_variables(10, start=0.0) core.minimize(lambda i: x[i]**2, over=range(10)) model = Model(core)
Model(core) finishes it; core.solve() is shorthand for both steps.
- add_con(*args, over=None, lcon=0.0, ucon=0.0, name=None)[source]¶
Add constraints, or add terms to constraints already added.
core.add_con(x[i] + x[i+1] for i in range(n - 1)) core.add_con(lambda i: x[i] + x[i+1], over=range(n - 1))
add_con(f, over) creates one row per index, lcon <= f(i) <= ucon, and returns a handle.
add_con(handle, f, over) adds terms into those rows: f(row) returns (row_index, expression) and the expression is added to that row. This is how a balance is assembled from many sources – every line and every generator at a bus – without materialising a sum per row. It mirrors the backend’s add_con / add_con! pair.
- add_expr(f, over=None, name=None)[source]¶
Name a reusable subexpression. Inlined at each use, so it adds no variables and no constraints. over may be a tuple for s[t, i].
- add_obj(f, over=None, name=None)[source]¶
Add sum(f(i) for i in over) to the objective.
Write it either way: add_obj(x[i]**2 for i in range(n)) or add_obj(lambda i: x[i]**2, over=range(n)).
- add_par(values, name=None)[source]¶
A block of parameters — fixed values usable in expressions, changeable afterwards with Model.set_parameters without rebuilding.
>>> import examodels as exa >>> core = exa.Core() >>> x = core.add_var(2) >>> p = core.add_par([3.0, 4.0]) >>> _ = core.add_obj(lambda i: (x[i] - p[i]) ** 2, over=range(2)) >>> m = exa.Model(core) >>> m.parameters(p) array([3., 4.]) >>> _ = m.set_parameters(p, [5.0, 6.0]) >>> m.parameters(p) array([5., 6.])
- add_var(*dims, start=0.0, lvar=None, uvar=None, tag=None, name=None)[source]¶
A block of decision variables, one dimension per argument.
x = core.add_var(10) # index as x[i] y = core.add_var(T, N) # index as y[t, i]
start, lvar and uvar are scalars or arrays of that shape.
>>> import examodels as exa >>> core = exa.Core() >>> x = core.add_var(3, start=1.0) >>> _ = core.add_obj(lambda i: (x[i] - 2.0) ** 2, over=range(3)) >>> exa.Model(core).get_start(x) array([1., 1., 1.])
- args¶
placeholders, when built with nargs=; empty otherwise
- nscen = 0¶
set by TwoStageCore; a plain core has none
The built model¶
- class examodels.Model(core=None, *args, **kwargs)[source]¶
A finished model, built from a Core — the backend’s ExaCore -> ExaModel.
Metadata (nvar, ncon, nnzj, nnzh, x0, lvar, …) is read straight off the backend rather than mirrored here, so nothing needs updating when the backend gains a field.
- get_lcon(handle)¶
Read the lcon of a variable, parameter or constraint block.
- get_lvar(handle)¶
Read the lvar of a variable, parameter or constraint block.
- get_start(handle)¶
Read the start of a variable, parameter or constraint block.
- get_ucon(handle)¶
Read the ucon of a variable, parameter or constraint block.
- get_uvar(handle)¶
Read the uvar of a variable, parameter or constraint block.
- get_value(handle)¶
Read the value of a variable, parameter or constraint block.
- set_lcon(handle, values)¶
Change the lcon of a block in place; the model is reused as is.
- set_lvar(handle, values)¶
Change the lvar of a block in place; the model is reused as is.
- set_parameters(block, values)[source]¶
Change a parameter block’s values in place; the model is reused as is.
- set_start(handle, values)¶
Change the start of a block in place; the model is reused as is.
- set_ucon(handle, values)¶
Change the ucon of a block in place; the model is reused as is.
- set_uvar(handle, values)¶
Change the uvar of a block in place; the model is reused as is.
- set_value(handle, values)¶
Change the value of a block in place; the model is reused as is.
Handles¶
- class examodels.Block(jlobj, axes, kind='variable')[source]¶
A block of variables or parameters, of one or more dimensions.
x = core.add_var(10) # x[i] y = core.add_var((T, N)) # y[t, i]
- property axes¶
- property shape¶
- class examodels.Constraint(jlobj, n, row_offset=0)[source]¶
A block of constraint rows. Pass it back to add_con to add terms to it.
- row_offset¶
added to a row index when adding terms — see Core.add_con
- class examodels.Expression(f, over)[source]¶
A reusable subexpression.
Subexpressions are inlined at each use — no auxiliary variable, no equality constraint — so this is held entirely on the Python side: s[i] just applies the function again. Uses sharing a structure share derivative code, exactly as if the backend had built them.
Index sets¶
Recipes¶
- examodels.recipe(nargs=1, **kwargs)[source]¶
A Core and its placeholders in one go — core, N = exa.recipe().
Identical to Core(nargs=…) followed by unpacking .args; which reads better depends on the model, so both spellings exist.
>>> import examodels as exa >>> core, n = exa.recipe() >>> x = core.add_var(n, start=0.0) >>> _ = core.add_obj(lambda i: (x[i] - 1.0) ** 2, over=exa.srange(0, n)) >>> exa.Model(core, 5).nvar 5 >>> exa.Model(core, 50).nvar 50
The compiler and the model cache¶
- examodels.compile_library(out, models, *examples, prefix=None, trim='safe', bundle=False, verbose=False, argfun=None)[source]¶
Compile models into a shared library under out, and return it.
models is either a single Core — with its example instantiation values as the remaining positional arguments, or none at all for a fixed model — or a mapping of name to core (or to a (core, *examples) tuple), which puts several models in one library.
out is a path, or `”@name”` — the sigil asks for the library to be installed on the CNLPMODELS_PATH search path, where both consumers find it by that name. A bare name with no @ is an ordinary relative path.
bundle=False (the default) emits a single small library linked against the Julia the compile ran on, which the consumer’s machine must also have. bundle=True carries a privatized copy of the runtime instead — around 80 MB, needing no Julia at the far end, and the only form loadable from Julia itself.
trim is passed to the compiler as its trimming mode.
argfun is for a model whose data should NOT cross the C boundary: the library carries the function, is handed one string or integer, and calls it to obtain the instantiation values. It has to be a named function belonging to a Julia package, so it is not reachable from Python – pass the data as example values instead, which is the Python-native route and needs no Julia at all. See _ARGFUN_HELP.
- examodels.compiler_available()[source]¶
Whether the compiler backend is present, without importing it.
- examodels.install_compiler()[source]¶
Install the compiler backend into this environment (one-off; needs a network).
- class examodels.CompiledLibrary(path, outdir, prefixes)[source]¶
What a compile produced: the library, where it went, and its model names.
Usable directly wherever a path is expected (str(lib), open(lib), os.fspath(lib)), since the path is what most callers want; the model names matter only for a library carrying more than one.
- outdir¶
the directory the compile wrote into (a bundle is a directory)
- path¶
- prefixes¶
the name each model answers to, in the order they were given
Core(cache=True) needs no API of its own — recording, lookup, compile and
load all hang off Core and Model; see The model cache — no compilation overhead after the first run.
Solvers and backends¶
- examodels.solve(obj, *args, **kwargs)¶
solve(model, …) — the same call as model.solve(…), in the argument order the backend uses.
- examodels.install_solver(name)[source]¶
Install a solver backend into this environment (one-off; needs a network).
- examodels.install_backend(name)[source]¶
Install an accelerator backend into this environment (one-off; needs a network). For “cuda” this is the whole stack a device solve needs — CUDA, MadNLP, MadNLPGPU and CUDSS — resolved into one environment. Restart Python afterwards: the environment cannot change under a running Julia.
Oracles, two-stage models and wrappers¶
- examodels.VectorNonlinearOracle(*, nvar, ncon, f, jac=None, hess=None, jac_rows=(), jac_cols=(), hess_rows=(), hess_cols=(), lcon=None, ucon=None, jvp=None, vjp=None, hvp=None, adapt=True)[source]¶
A constraint block you evaluate yourself.
f(c, x) fills c with residuals. Supply either explicit derivatives – jac(vals, x) and hess(vals, x, y) with their sparsity patterns – or the matrix-free products jvp(Jv, x, v), vjp(Jtv, x, w), hvp(Hv, x, w, v).
adapt=True (the default here) copies the arrays to the host before each call, which is what a Python callback needs. Set it False only for a callback that can run on the device – which a Python one cannot.
- examodels.ScalarNonlinearOracle(*, nvar, f, grad, hvp=None, hess_rows=(), hess_cols=(), adapt=True)[source]¶
An objective term you evaluate yourself: f(x) and grad(g, x).
- examodels.has_matfree_jac(oracle)[source]¶
True when the oracle supplies Jacobian-vector products instead of a matrix.
- examodels.has_matfree_hess(oracle)[source]¶
True when the oracle supplies Hessian-vector products instead of a matrix.
- examodels.TwoStageCore(nscen, backend=None)[source]¶
A core for a two-stage stochastic program with nscen scenarios.
core = TwoStageCore(3) d = add_var(core, 2) # design, shared v = add_var(core, EachScenario(), 4) # recourse, per scenario add_con(core, EachScenario(), lambda i: v[i] - d[0], over=range(4))
Everything else about it is an ordinary Core; only how it starts differs.
- examodels.get_var_scen(model)[source]¶
Which scenario each variable belongs to (0 for the shared first stage).
- examodels.get_con_scen(model)[source]¶
Which scenario each constraint row belongs to (0 for the first stage).
- examodels.new_tag(name, kind='variable')[source]¶
Define a tag to mark variables or constraints with.
The backend dispatches on a tag’s type, so one is created here rather than passed as a value. name is checked before use: it is the only caller-supplied string in this package that reaches the backend as source rather than as data, and an unchecked one would let any Julia code through.
CuPy interchange¶
Elementwise functions¶
sin, cos, exp, log, … are generated from the backend’s own registry of supported
operators, so dir(examodels) is the authoritative list. Use these rather than math or
numpy equivalents inside a traced expression.