DSL Models¶
Describe the problem once with Variable, Parameter, and constraints, then compile it to a backend. The example below is a binary program with a predicted cost and linear constraints:
import numpy as np
from pyepo import EPO, dsl
A = np.array([[3, 4, 3, 6, 4],
[4, 5, 2, 3, 5],
[5, 4, 6, 2, 3]])
b = np.array([12, 10, 15])
x = dsl.Variable(5, vtype=EPO.BINARY) # decision variables
c = dsl.Parameter(5) # the predicted cost
prob = dsl.Problem(dsl.Maximize(c @ x), [A @ x <= b])
optmodel = prob.compile(backend="gurobi") # compile to a solver backend
The compiled model is an optModel. During training, pyepo.func calls setObj and solve.
All backends share this interface and are selected with backend=. Gurobi and COPT are commercial solvers. Pyomo and OR-Tools can use open solvers such as HiGHS, GLPK, CBC, and SCIP. MPAX solves linear and quadratic programs on the GPU.
compile forwards keyword arguments to the backend. solver= applies only to the generic backends (pyomo / ortools) and names the solver they run. timelimit= sets a time limit in seconds where the backend supports one. Any other keyword is passed through as a native solver parameter where the backend accepts one:
Backend |
|
|
Other keywords |
|---|---|---|---|
|
not applicable |
maps to |
any native Gurobi parameter, e.g. |
|
not applicable |
maps to |
any native COPT parameter |
|
open solver name (default |
maps to the chosen solver’s own option for GLPK, CBC, SCIP, HiGHS, Ipopt, Gurobi, or CPLEX. With any other solver, pass the native option as a keyword instead |
passed through as solver options |
|
pywraplp solver name (default |
supported |
not accepted |
|
not applicable |
accepted and ignored (MPAX exposes no time-limit setting) |
not accepted |
prob.compile(backend="pyomo", solver="appsi_highs") # an open solver via Pyomo
prob.compile(backend="gurobi", timelimit=10) # time limit (seconds)
prob.compile(backend="gurobi", MIPGap=0.01) # native solver parameters pass through
Variables¶
A Variable takes a shape (an integer or a tuple), an optional vtype, and bounds. A problem declares exactly one Parameter, the predicted cost.
x = dsl.Variable(5) # continuous (the default)
x = dsl.Variable(5, vtype=EPO.BINARY) # also INTEGER, CONTINUOUS, or a per-entry list
x = dsl.Variable((3, 3)) # multi-dimensional (a tuple shape)
x = dsl.Variable(5, lb=0, ub=1) # bounds, scalar or array
c = dsl.Parameter(5) # the predicted cost
Objectives¶
Whether a coefficient is predicted or known is determined by its type: a Parameter is predicted (c), while a numpy array is fixed (d, Q). The predicted cost enters linearly. A known quadratic term may also be added. Below, d is a numpy array, y is another Variable, Q is a numpy matrix, and k is an index.
dsl.Minimize(c @ x) # inner product (scalar); or dsl.Maximize(c @ x)
dsl.Minimize((c * x).sum()) # elementwise then reduce; same objective as c @ x
dsl.Minimize(c @ x + d @ y) # predict c on x, keep known d on y
dsl.Minimize(c @ x[:k] + d @ x[k:]) # predict part of one variable, fix the rest
dsl.Minimize((d + c) @ x) # a known base d plus the predicted c
dsl.Minimize(c @ x + x @ Q @ x) # predicted linear plus a known quadratic
c @ x is a 1-D inner product. For a multi-dimensional cost, use (c * x).sum() (elementwise, then reduced). A quadratic objective term needs a backend with QP support: Gurobi, COPT, MPAX, or Pyomo with a QP-capable solver.
Note
A quadratic objective term is solve-only. Compiling warns, and pyepo.func training methods and pyepo.metric metrics reject the model with an error. Quadratic constraints carry no such restriction.
Constraints¶
Constraints are fixed across instances. Only the cost is predicted. Pass constraints as a list to Problem.
A @ x <= b # linear: <=, >=, ==
x.sum() == 1 # reduction
x.sum(axis=1) == 1 # per-axis sums, e.g. an assignment
x @ Q @ x <= gamma # quadratic (Gurobi, COPT, or QP-capable Pyomo)
For a linear or quadratic objective with fixed constraints, the DSL is all you need. For cases the DSL cannot express, see Custom Models.