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.