The pyepo.func.jax module provides JAX-compatible versions of the training methods (SPOPlus, DPO, PFY). These losses use jax.custom_vjp to handle the gradient through the optimization model.
Importing:
- PyTorch:
from pyepo.func import SPOPlus, DPO, PFY - JAX:
from pyepo.func.jax import SPOPlus, DPO, PFY
Solver Backends:
- MPAX: Solved natively. The PDHG solve is JAX-traceable, allowing the entire training step to be accelerated with
jax.jit. - Non-MPAX (GurobiPy, COPT, Pyomo, OR-Tools): These are reached via
jax.pure_callback, wrapping the CPU solver. While the training step can still be wrapped in jax.jit, the solver itself will run on the CPU.
import jax
import jax.numpy as jnp
import optax
from flax import linen as nn
import pyepo
from pyepo.data.dataset import optDataset
from pyepo.func.jax import SPOPlus
# Example setup for a 5x5 grid shortest path
grid = (5, 5)
optmodel = pyepo.model.shortestPathModel(grid)
# Synthetic data generation
x, c = pyepo.data.shortestpath.genData(
num_data=1000, num_features=5, grid=grid, deg=4, noise_width=0.5, seed=135,
)
ds = optDataset(optmodel, x, c)
xj = jnp.asarray(x, jnp.float32)
cj, wj, zj = (jnp.asarray(a, jnp.float32) for a in (ds.costs, ds.sols, ds.objs))
# Predictor and Loss
predmodel = nn.Dense(optmodel.num_cost)
params = predmodel.init(jax.random.PRNGKey(0), xj[:1])
spo = SPOPlus(optmodel, reduction="mean")
optimizer = optax.adam(1e-2)
opt_state = optimizer.init(params)
# End-to-end training loop
for epoch in range(10):
grads = jax.grad(lambda p: spo(predmodel.apply(p, xj), cj, wj, zj))(params)
updates, opt_state = optimizer.update(grads, opt_state)
params = optax.apply_updates(params, updates)