jaxsr.mip#
Embed fitted JAXSR models in mixed-integer optimization models.
A JAXSR model is a weighted sum of basis terms, so it can be written into an
optimization model term by term. When every selected term is constant, linear,
a hinge max(0, x - k), abs(x), a categorical indicator, or an
indicator times a continuous feature, the model is piecewise linear and the
embedding is an exact mixed-integer linear program (MILP):
Term |
Formulation |
|---|---|
|
written directly |
|
aux |
|
aux |
|
one-hot binary for category |
|
exact McCormick product of binary and |
The big-M constants come from the input bounds, which are therefore required
and are enforced by the embedding (the surrogate is only valid inside them).
Polynomial, interaction, transcendental, power-law and ratio terms are not
MILP-representable; pass allow_nonlinear=True to write them as nonlinear
expressions (giving an MIQCP or MINLP).
Two backends are supported: Pyomo (to_pyomo()) and discopt
(to_discopt()). Both share one formulation, so they produce the same
constraints.
- jaxsr.mip.MILP_TERM_TYPES = frozenset({'categorical_interaction', 'constant', 'hinge', 'indicator', 'linear'})#
Term types that have an exact MILP formulation.
- jaxsr.mip.NONLINEAR_TERM_TYPES = frozenset({'interaction', 'polynomial', 'polynomial_interaction', 'power', 'ratio', 'transcendental'})#
Term types that can be written as nonlinear expressions (
allow_nonlinear=True).
- jaxsr.mip.milp_incompatible_terms(model: SymbolicRegressor) list[str]#
List the selected terms of a fitted model that are not MILP-representable.
- Parameters:
model (SymbolicRegressor) – A fitted model.
- Returns:
names – Names of selected terms with no exact MILP formulation. An empty list means
to_pyomo()andto_discopt()produce a pure MILP.- Return type:
list of str
- Raises:
TypeError – If
modelis not a SymbolicRegressor.ValueError – If the model is not fitted or uses parametric basis functions.
- jaxsr.mip.to_pyomo(model: SymbolicRegressor, block: Any = None, inputs: dict[str, Any] | None = None, output: Any = None, bounds: dict[str, tuple[float, float]] | list[tuple[float, float]] | None = None, output_sense: str | None = None, allow_nonlinear: bool = False) Any#
Write a fitted model into a Pyomo block.
- Parameters:
model (SymbolicRegressor) – A fitted model.
block (pyomo Block, optional) – Block to populate, e.g.
m.surrogate = pyo.Block(). If None, a newConcreteModelis created and returned.inputs (dict, optional) – Maps feature names to your own Pyomo variables or expressions; each is linked to the block’s input with an equality constraint. Categorical inputs are integer-coded
0..K-1.output (pyomo Var or expression, optional) – Linked to the block’s output with an equality constraint.
bounds (dict or list, optional) – Input bounds, as
{name: (lo, hi)}or a list in feature order. Defaults to the library’sfeature_bounds, then the training data range. The block enforces them.output_sense ({None, "min", "max"}) – If the output is only ever minimized (or only maximized) in your problem, hinge and
absterms whose coefficient makes them convex in that direction are written without binaries. Leave None (the default) for an exact formulation that is valid in any problem.allow_nonlinear (bool) – If True, write non-MILP terms as nonlinear Pyomo expressions.
- Returns:
block – The populated block, with components
aux(inputsaux["in_<name>"], outputaux["out"]and auxiliary variables),binaries,integersandcons.block.jaxsr_inputsandblock.jaxsr_outputpoint to the input and output variables.- Return type:
pyomo Block
- Raises:
ImportError – If Pyomo is not installed.
ValueError – If the model has terms that cannot be embedded, has non-MILP terms and
allow_nonlinearis False, or bounds are missing or invalid.
Examples
>>> import pyomo.environ as pyo >>> m = pyo.ConcreteModel() >>> m.x = pyo.Var(bounds=(0, 1)) >>> m.sr = pyo.Block() >>> to_pyomo(model, m.sr, inputs={"x": m.x}) >>> m.obj = pyo.Objective(expr=m.sr.jaxsr_output)
- class jaxsr.mip.DiscoptEmbedding(inputs: dict[str, Any], output: Any, onehot: dict[tuple[str, Any], Any], binaries: list[Any])#
Bases:
objectVariables created by
to_discopt().- inputs#
Feature name to input variable (integer-coded for categorical features).
- Type:
dict
- output#
Variable equal to the model prediction.
- Type:
discopt Variable
- onehot#
(feature name, category)to the one-hot binary for that category.- Type:
dict
- binaries#
All binary variables the embedding created.
- Type:
list
- inputs: dict[str, Any]#
- output: Any#
- onehot: dict[tuple[str, Any], Any]#
- binaries: list[Any]#
- jaxsr.mip.to_discopt(model: SymbolicRegressor, dm_model: Any, inputs: dict[str, Any] | None = None, output: Any = None, bounds: dict[str, tuple[float, float]] | list[tuple[float, float]] | None = None, output_sense: str | None = None, allow_nonlinear: bool = False, prefix: str = 'jaxsr') DiscoptEmbedding#
Write a fitted model into a discopt model.
- Parameters:
model (SymbolicRegressor) – A fitted model.
dm_model (discopt.Model) – Model to add variables and constraints to.
inputs (dict, optional) – Maps feature names to your own discopt variables or expressions; each is linked to the embedding’s input with an equality constraint. Categorical inputs are integer-coded
0..K-1.output (discopt Variable or expression, optional) – Linked to the embedding’s output with an equality constraint.
bounds (dict or list, optional) – Input bounds, as
{name: (lo, hi)}or a list in feature order. Defaults to the library’sfeature_bounds, then the training data range. The embedding enforces them.output_sense ({None, "min", "max"}) – See
to_pyomo().allow_nonlinear (bool) – If True, write non-MILP terms as nonlinear discopt expressions.
prefix (str) – Prefix for every variable name, so several models can be embedded in one discopt model (default
"jaxsr").
- Returns:
embedding – The input, output and binary variables created.
- Return type:
- Raises:
ImportError – If discopt is not installed.
ValueError – If the model has terms that cannot be embedded, has non-MILP terms and
allow_nonlinearis False, or bounds are missing or invalid.
Examples
>>> import discopt >>> m = discopt.Model("design") >>> emb = to_discopt(model, m) >>> m.minimize(emb.output) >>> result = m.solve()