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

build()[source]

Finish this core, returning a Model. Same as Model(core).

nscen = 0

set by TwoStageCore; a plain core has none

solve(solver=None, **options)[source]

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.

constraints(x)[source]
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.

gradient(x)[source]
objective(x)[source]
parameters(block)[source]

Current values of a parameter 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.

solve(solver=None, **options)[source]
violation(x)[source]

Largest constraint violation at x.

Not max|c(x)|: for a one-sided constraint the value itself is unbounded and says nothing about feasibility – only how far outside its own bounds each row sits does.

class examodels.Solution(raw, elapsed=nan)[source]

Result of a solve. Fields come from the solver’s own result object.

sol[block] gives that block’s values.

elapsed

solvers do not agree on reporting it

Type:

wall-clock seconds, measured here

multipliers(constraint)[source]

Duals of a constraint block.

multipliers_L(block)[source]

Duals of a variable block’s lower bounds.

multipliers_U(block)[source]

Duals of a variable block’s upper bounds.

property success

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.

class examodels.Node(jlobj)[source]

Handle to a backend expression node.

property julia_type

Full parametric backend type — the structural fingerprint of the expression.

Index sets

examodels.product(*axes)[source]

A rectangular index set: product(range(T), range(N)).

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
examodels.srange(start, stop=None)[source]

srange(stop) or srange(start, stop) — half-open, like range.

Use it wherever an index set involves a placeholder; plain range stays correct everywhere else, and reads better.

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.available_solvers()[source]
examodels.install_solver(name)[source]

Install a solver backend into this environment (one-off; needs a network).

examodels.backends()[source]

Accelerator backends this package knows how to construct.

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_nscen(model)[source]

How many scenarios a two-stage model has.

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.

examodels.WrapperNLPModel(model)[source]

Buffer a model’s evaluations through host arrays, for a solver that needs it.

examodels.TimedNLPModel(model)[source]

Wrap a model so it records how long each evaluation takes.

examodels.CompressedNLPModel(model)[source]

Wrap a model with duplicate Jacobian and Hessian entries merged.

CuPy interchange

examodels.as_cupy(array)[source]

View a backend device array as a CuPy array, sharing the same memory.

The backend keeps ownership: the result is a view, so writing through it writes into the model.

examodels.from_cupy(array)[source]

View a CuPy array as a backend device array, sharing the same memory.

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.