Oracle constraints¶
An oracle is a block of constraints, or an objective term, that you evaluate yourself — useful when the residual comes from a simulation, an external solver, or code that is not expressible as an algebraic expression.
import examodels as exa
core = exa.Core()
x = exa.add_var(core, 2, start=0.5)
def f(c, x):
c[0] = x[0]**2 + x[1]**2 - 1.0
def jac(v, x):
v[0], v[1] = 2 * x[0], 2 * x[1]
def hess(v, x, y):
v[0] = v[1] = 2 * y[0]
oracle = exa.VectorNonlinearOracle(
nvar=2, ncon=1, f=f, jac=jac, hess=hess,
jac_rows=[1, 1], jac_cols=[1, 2],
hess_rows=[1, 2], hess_cols=[1, 2],
lcon=[0.0], ucon=[0.0])
exa.add_con(core, oracle)
Sparsity patterns are declared once, with 1-based indices, and never change.
Instead of explicit derivatives you may supply matrix-free products — jvp(Jv, x, v),
vjp(Jtv, x, w) and hvp(Hv, x, w, v). has_matfree_jac and has_matfree_hess report
which path an oracle uses.
An objective term works the same way:
o = exa.ScalarNonlinearOracle(
nvar=2,
f=lambda x: float(x[0]**2 + x[1]**2),
grad=lambda g, x: g.__setitem__(slice(None), 2 * x))
exa.add_obj(core, o)
model = exa.Model(core)
adapt¶
adapt=True — the default here, and the opposite of the backend’s — copies arrays to the
host before each call. A Python callback cannot run inside a device kernel, so this is what
makes an oracle usable at all from Python. Set it False only for a callback that is
genuinely device-capable, which a Python one is not.