External Optimizer Adapters API#
Thin adapter functions that drive a mature external optimizer (SciPy, optax)
from a SpaceCore Functional. They evaluate the objective
through F.value and convert the metric (Riesz) gradient F.grad to the
coordinate gradient an external optimizer expects with X.riesz – the
identity on a Euclidean space and mandatory on a weighted one. The external
optimizer owns the loop, line search, and convergence; the adapter only
translates the objective and its geometry. This adapter surface is specified by
ADR-018.
SciPy#
Minimize a SpaceCore functional with |
|
Wolfe line search along |
minimize_scipydrivesscipy.optimize.minimize(), flattening elements to and from SciPy’s coordinate vector and supplyingX.riesz(F.grad(x))as the Jacobian. Returns the SciPyOptimizeResultwith an addedx_elementfield (the minimizer unflattened intoF.domain).line_search_scipydrivesscipy.optimize.line_search()along a search direction with the same gradient handoff.
optax#
Run an optax optimizer on a SpaceCore functional with pytree pass-through. |
minimize_optaxruns the canonical optax update loop with pytree pass-through (no flatten/unflatten); the coordinate gradient is the sameX.riesz(F.grad(x))handoff. Requires a JAX-backed domain and the optionaloptaxdependency (pip install spacecore[optax]).
Information lost at the external boundary#
Structure. SciPy sees a flat coordinate vector;
boundsandconstraintsare expressed in flattened coordinates.Geometry. The external optimizer works in the flat Euclidean coordinate metric; the domain geometry survives only through the
X.rieszgradient conversion.Field. The SciPy adapters require a real domain and reject complex spaces.
Tracing. Jitted external solves may require a context built with
check_level="none".
Autodoc#
- spacecore.minimize_scipy(F, x0, *, method='L-BFGS-B', jac=True, **kw)[source]#
Minimize a SpaceCore functional with
scipy.optimize.minimize().The objective is evaluated through
F.valueand the Jacobian throughX.riesz(F.grad(x))– the metric-to-coordinate gradient handoff that is the identity on a Euclidean space and mandatory on a weighted one. SpaceCore elements are flattened to and from SciPy’s flat coordinate array withF.domain.flatten/unflatten.- Parameters:
F (Functional) – Objective with a real, inner-product domain
X = F.domain.F.gradmust be implemented unless a gradient-freejacis selected.x0 (array-like) – Initial guess, an element of
F.domain.method (str, optional) – SciPy
minimizemethod. Default"L-BFGS-B".jac (bool, str, callable, or None, optional) – Gradient policy.
True(default) supplies SpaceCore’sX.riesz(F.grad(x))coordinate gradient.FalseorNonelets SciPy approximate the gradient by finite differences. A real finite-difference string ("2-point"or"3-point") or a callable over the flat coordinate vector is forwarded to SciPy unchanged. The complex-step mode"cs"is rejected because it evaluates the objective at complex perturbations the real domain cannot represent.**kw – Forwarded to
scipy.optimize.minimize()(e.g.bounds,constraints,tol,options).boundsandconstraintsare interpreted in flattened coordinates. The SciPyargsparameter is not supported (aFunctionaltakes only its domain element).
- Returns:
The SciPy result, unchanged, with one added field
x_element: the minimizerresult.xunflattened back into an element ofF.domain.result.xstays the flat coordinate array, by SciPy convention.- Return type:
scipy.optimize.OptimizeResult
- Raises:
TypeError – If
Fis not aFunctionalor its domain has no inner-product geometry.ValueError – If
F.domainis over the complex field, ifjac="cs", or if the unsupported SciPyargsparameter is passed.
See also
line_search_scipyWolfe line search along a direction with the same handoff.
spacecore.optimize.minimize_optaxOptax loop with pytree pass-through.
Notes
The external optimizer owns iteration, line search, and convergence; this adapter only translates the objective and its geometry. The minimizer location of a smooth problem does not depend on the metric (a metric gradient and the coordinate gradient vanish together), but SciPy’s steps do: feeding SciPy the raw metric gradient
F.gradon a weighted space is inconsistent withfunand degrades or breaks convergence.X.rieszremoves that trap.Examples
Minimize
f(x) = 1/2 x^T diag(3, 1) x - 3 x_0 - 2 x_1on a Euclidean space; the minimizer is(1, 2).>>> import numpy as np >>> import spacecore as sc >>> ctx = sc.Context(sc.NumpyOps(), dtype=np.float64) >>> X = sc.DenseCoordinateSpace((2,), ctx) >>> Q = sc.DenseLinOp(ctx.asarray([[3.0, 0.0], [0.0, 1.0]]), X, X, ctx) >>> linear = sc.InnerProductFunctional(ctx.asarray([-3.0, -2.0]), X) >>> F = sc.LinOpQuadraticForm(Q, linear) >>> result = sc.minimize_scipy(F, X.zeros(), options={"gtol": 1e-10}) >>> bool(result.success) True >>> np.allclose(np.asarray(result.x_element), [1.0, 2.0]) True
- spacecore.line_search_scipy(F, x, d, **kw)[source]#
Wolfe line search along
dwithscipy.optimize.line_search().Evaluates
F.valueand the coordinate gradientX.riesz(F.grad(x))over the flat coordinate representation, so SciPy’s slopecoordinate_gradient . dis the correct directional derivativedf(d) = <F.grad(x), d>_Xeven on a weighted space.- Parameters:
F (Functional) – Objective with a real, inner-product domain
X = F.domain.x (array-like) – Current iterate, an element of
F.domain.d (array-like) – Search direction, an element of
F.domaininterpreted as a coordinate displacement (the new iterate isx + alpha * d). The adapter does not transformd: both-F.grad(x)(natural/metric steepest descent) and-X.riesz(F.grad(x))(coordinate steepest descent) are valid descent directions.**kw – Forwarded to
scipy.optimize.line_search()(e.g.gfk,old_fval,c1,c2,amax). The SciPyargsparameter is not supported (aFunctionaltakes only its domain element).
- Returns:
The SciPy
line_searchtuple(alpha, fc, gc, new_fval, old_fval, new_slope).alphais the step length and isNonewhen the line search fails to satisfy the Wolfe conditions.- Return type:
tuple
- Raises:
TypeError – If
Fis not aFunctionalor its domain has no inner-product geometry.ValueError – If
F.domainis over the complex field, or if the unsupported SciPyargsparameter is passed.
Examples
A descent step on a convex quadratic returns a positive step length.
>>> import numpy as np >>> import spacecore as sc >>> ctx = sc.Context(sc.NumpyOps(), dtype=np.float64) >>> X = sc.DenseCoordinateSpace((2,), ctx) >>> Q = sc.DenseLinOp(ctx.asarray([[3.0, 0.0], [0.0, 1.0]]), X, X, ctx) >>> F = sc.LinOpQuadraticForm(Q) >>> x = ctx.asarray([1.0, 1.0]) >>> d = X.scale(-1.0, F.grad(x)) >>> alpha = sc.line_search_scipy(F, x, d)[0] >>> bool(alpha > 0.0) True
- spacecore.minimize_optax(F, x0, optimizer, *, steps, callback=None)[source]#
Run an optax optimizer on a SpaceCore functional with pytree pass-through.
Executes the canonical optax update loop for
stepsiterations:grad = X.riesz(F.grad(params)) # metric -> coordinate gradient updates, opt_state = optimizer.update(grad, opt_state, params) params = optax.apply_updates(params, updates)
The optax optimizer owns the update rule (learning rate, momentum, preconditioning); this adapter only supplies the coordinate gradient and runs the fixed-length loop. There is no convergence test – optax is a gradient-transformation library, so iteration count is the caller’s contract.
- Parameters:
F (Functional) – Objective with an inner-product domain
X = F.domainand an implementedF.grad.x0 (pytree) – Initial parameters, an element of
F.domain. A raw array or tuple/list/dict of arrays is used as-is; a boundTreeElementis normalized to its raw pytree.optimizer (optax.GradientTransformation) – An optax optimizer such as
optax.adam(1e-2).steps (int) – Number of update steps to run. Must be non-negative.
callback (callable, optional) – Called as
callback(step, params)after each update, e.g. to record a loss trajectory.
- Returns:
The final parameters, an element of
F.domain.- Return type:
pytree
- Raises:
TypeError – If
Fis not aFunctional, its domain has no inner-product geometry, or its domain is not JAX-backed (optax produces JAX arrays).ValueError – If
stepsis negative.ImportError – If
optaxis not installed (pip install spacecore[optax]).
See also
spacecore.optimize.minimize_scipySciPy
minimizewith the same handoff.
Notes
On a Euclidean space
X.rieszis the identity. On a weighted space it multiplies the metric gradient by the diagonal weights, which is exactly the coordinate gradient optax’s Euclidean update expects (ADR-018).Examples
import numpy as np import optax import spacecore as sc ctx = sc.Context(sc.JaxOps(), dtype=np.float32, check_level="none") X = sc.DenseCoordinateSpace((2,), ctx) Q = sc.DenseLinOp(ctx.asarray([[3.0, 0.0], [0.0, 1.0]]), X, X, ctx) linear = sc.InnerProductFunctional(ctx.asarray([-3.0, -2.0]), X) F = sc.LinOpQuadraticForm(Q, linear) x_star = sc.minimize_optax(F, X.zeros(), optax.adam(1e-1), steps=500) # x_star is approximately (1.0, 2.0)