Learning to Solve Parametric Mixed-Integer Optimal Control Problems via Differentiable Predictive Control
Reference implementation and reproduction scripts for the paper Learning to Solve Parametric Mixed-Integer Optimal Control Problems via Differentiable Predictive Control. The method extends Differentiable Predictive Control to systems with mixed-integer decision variables.
We provide an instructive live Google Colab example code of MI-DPC with a similar problem setup
.
Online mixed-integer optimal control (MI-OCP) requires solving an NP-hard mixed-integer quadratic program (MIQP) at every sampling instant; computational cost grows quickly with problem size (horizon length). MI-DPC trains a neural control policy
$\pi_\theta$ offline to map parameters$\xi_k = [x_k^\top, d_k^\top, \dots, d_{k+N}^\top]^\top$ directly to continuous and integer control decisions$[u_k^\top, \delta_k^\top]^\top$ , constituting an explicit solution and thus avoiding online optimization.
Training is self-supervised: no optimal trajectory labels are needed. Instead, the MPC objective is minimized by differentiating through an
$N$ -step rollout of the system dynamics using backpropagation through time. Integer decisions$\delta_k \in \mathbb{Z}$ are enforced with a differentiable rounding (quantization) layer where the gradient is approximated using a differentiable surrogate of the quantization function.
This repository implements MI-DPC with three different rounding strategies and an imitation-learning baseline trained on optimal trajectory labels.
| Approach | Script | Description |
|---|---|---|
| Optimal baseline (MI-MPC) | _1_CPLEX.py |
Exact MIQP solved online with CPLEX branch-and-bound. |
| MI-DPC — Sigmoid STE | _2_sigmoid.py |
Nearest-integer rounding; sigmoid straight-through gradient estimation. |
| MI-DPC — Gumbel-Softmax STE | _3_softmax.py |
Categorical approach; hard one-hot (argmax) forward, soft (softmax) backward. |
| MI-DPC — Learnable threshold | _4_learnable_threshold.py |
Second network predicts correction and per-variable threshold (Tang et al., 2025). |
| Imitation learning | _6_*.py + _7_*.py |
Same network as Sigmoid STE, fitted to MIQP labels. |
Second-order LTI thermal system with two heat pumps and a bank of heating rods. Sampling period
with
- States
$x_1, x_2$ [kWh] — tank energies, box-constrained to$[0, 8.4]$ and$[0, 3.6]$ . - Continuous inputs
$u_1, u_2$ [kW] — heat pumps,$u_i \ge 0$ ,$0 \le u_1 + u_2 \le 8$ . - Integer input
$\delta_1 \in {0,1,2,3}$ [kW] — active 1 kW heating rods in tank$x_2$ . - Disturbances
$d_1, d_2$ [kW] — known over the horizon. - Setpoint
$r = [4.2, 1.8]^\top$ kWh; weights$P = Q = I$ ,$R = \mathrm{diag}(0.5, 0.5)$ ,$\rho = 0.1$ . - Horizons
$N \in {10, 15, 20, 25, 30, 35, 40}$ .
Heating rods are weighted cheaper than heat pumps so the integer input is exercised frequently.
The control objective is defined by a loss function where a control error between state
with PenaltyLoss on variable('X') and variable('U'), penalizing violations,
Closed-loop trajectories for
Phase plot for
Table 1 from the paper (20 initial conditions, 1873 steps):
| Approach | Metric | ||||||
|---|---|---|---|---|---|---|---|
|
MI-DPC Sigmoid STE |
6.82 | 4.60 | 4.19 | 3.95 | 3.89 | 3.85 | |
| RSM | 14.31 % | 4.14 % | 4.37 % | 1.42 % | 1.15 % | – | |
| MIT | 0.0002 s | 0.0002 s | 0.0002 s | 0.0002 s | 0.0002 s | 0.0002 s | |
| NTP | 82 603 | 84 003 | 85 403 | 86 803 | 88 203 | 91 003 | |
| TT | 231.6 s | 347.5 s | 214.4 s | 440.8 s | 410.3 s | 444.1 s | |
|
MI-DPC Softmax STE |
6.76 | 4.79 | 4.11 | 3.93 | 3.90 | 3.86 | |
| RSM | 13.56 % | 8.05 % | 2.36 % | 1.11 % | 1.36 % | – | |
| MIT | 0.0002 s | 0.0002 s | 0.0002 s | 0.0002 s | 0.0002 s | 0.0002 s | |
| NTP | 83 026 | 84 426 | 85 826 | 87 226 | 88 626 | 91 426 | |
| TT | 223.3 s | 281.6 s | 373.3 s | 432.7 s | 317.3 s | 393.4 s | |
|
MI-DPC Learnable threshold |
6.42 | 4.53 | 4.08 | 3.90 | 3.86 | 3.84 | |
| RSM | 8.96 % | 2.69 % | 1.68 % | 0.45 % | 0.41 % | – | |
| MIT | 0.0004 s | 0.0004 s | 0.0004 s | 0.0004 s | 0.0004 s | 0.0004 s | |
| NTP | 78 191 | 80 091 | 81 991 | 83 891 | 85 791 | 89 591 | |
| TT | 429.9 s | 573.8 s | 664.6 s | 845.7 s | 821.4 s | 1080.9 s | |
|
Imitation learning Sigmoid STE |
5.87 | 4.48 | 4.06 | 3.96 | 3.89 | 4.99 | |
| RSM | 0.52 % | 1.52 % | 1.15 % | 1.92 % | 1.27 % | – | |
| MIT | 0.0002 s | 0.0002 s | 0.0002 s | 0.0002 s | 0.0002 s | 0.0002 s | |
| NTP | 82 603 | 84 003 | 85 403 | 86 803 | 88 203 | 91 003 | |
| TT | 1826.9 s | 2335.2 s | 3645.9 s | 4405.6 s | 7053.2 s | 7238.9 s | |
| Optimal (MIQP) | 5.84 | 4.41 | 4.01 | 3.88 | 3.84 | † | |
| MIT | 0.0031 s | 0.0085 s | 0.0421 s | 0.2507 s | 1.3140 s | † | |
| FUP | 0 % | 0 % | 0 % | 0.04 % | 2.44 % | † |
Metrics: RSM — relative suboptimality margin vs. MIQP; MIT — mean inference time; NTP — number of trainable parameters; TT — training time (imitation learning includes label generation, capped at 2 h); FUP — fraction of steps where the MIQP solve exceeded 15 s.
† CPLEX did not finish
The integer head outputs a relaxed value
Nearest-integer rounding is applied in the forward pass:
The backward pass replaces the zero gradient of rounding with the derivative of a sigmoid surrogate. With slope
Larger
The STE is implemented as a detach trick (no custom autograd function):
def _relaxed_round(x, slope=10.0):
backward = x - torch.floor(x) - 0.5 # fractional part minus threshold t
return torch.round(x) + (torch.sigmoid(slope*backward) - torch.sigmoid(slope*backward).detach())Limitation: sigmoid STE assumes evenly spaced feasible integers (e.g.
Integrality is cast as categorical classification. For each integer input
The forward pass selects the arg-max category (hard one-hot
Because one-hot encoding is non-differentiable, the backward pass uses the soft probabilities
Gumbel noise encourages exploration during training; it is disabled at evaluation (enable_gumbels = False in _5_test_models.py). This repository uses
Following Tang et al. (2025), a second network on continuous_update=True, the correction network also refines
Sampled offline: training_data/extended_disturbances_60.pt holds 40 281 windows of length 40; first 24 000 for training, next 4000 for validation. Each horizon uses the first loads_matrix.mat.
Adam, lr
_5_test_models.py rolls out 20 initial conditions (utils/initial_conditions.py) over 1873 steps on CPU (torch.set_num_threads(1), GC disabled during timing). MIT = total wall time / PenaltyLoss.calculate_objectives; RSM =
- Python 3.10+
- PyTorch 2.8 (CUDA recommended for training)
- NeuroMANCER 1.5.6
- NumPy, SciPy, Matplotlib, tqdm
- CVXPY + mixed-integer solver (CPLEX 22.1.2 in the paper)
- LaTeX with
pdflatexfor PGF export in_8_plots.py
_0 … _8 *.py pipeline scripts (execution order)
run_*.sh parallel launchers (core-pinned)
utils/ initial conditions, STE, rounding modules
training_data/ synthetic disturbances (40281, 40, 2)
loads_matrix.mat test disturbance trajectories
CPLEX_inference_data/ exact MIQP rollouts
imitation_learning_data/ MIQP-labelled datasets
training_outputs/ trained policies and metadata
simulation_data/ closed-loop rollouts and metrics
plots/ figures (PDF, PGF, PNG previews)
logs/, test_models.log reference run logs
If you use this methodology in your work, please cite our work as:
@article{boldocky2025learning,
title={Learning to solve parametric mixed-integer optimal control problems via differentiable predictive control},
author={Boldock{\'y}, J{\'a}n and Javan, Shahriar Dadras and Gulan, Martin and M{\"o}nnigmann, Martin and Drgo{\v{n}}a, J{\'a}n},
journal={arXiv preprint arXiv:2506.19646},
year={2025}
}Supported by Horizon Europe grant no. 101079342 (FrontSeat) and the Ralph O'Connor Sustainable Energy Institute at Johns Hopkins University.







