Rich model representations#
A discopt Model renders itself as a typeset optimization problem in standard form [Williams, 2013] — minimize f(x) / subject to g(x) ≤ 0 / variable domains. In Jupyter the model displays automatically; you can also export the LaTeX or HTML for papers and docs.
Let us build a small mixed-integer nonlinear program and just look at it.
import discopt.modeling as dm
m = dm.Model("blend")
x = m.continuous("x", lb=0, ub=10)
y = m.binary("y")
m.minimize((x - 3) ** 2 + 2 * y)
m.subject_to(x + 5 * y >= 4)
m.subject_to(dm.exp(x) <= 100)
m # renders as a typeset problem (HTML/LaTeX via MathJax)
blend (2 variables, 2 constraints)The constraints are shown in their natural form (x + 5y ≥ 4), even though they are stored internally in normalised g(x) ≤ 0 form. To get the markup directly — e.g. to paste into a paper — use to_latex():
print(m.to_latex())
\begin{aligned}
& \text{minimize} \quad && (x - 3)^{2} + 2 y \\
& \text{subject to} \quad && x + 5 y \ge 4 \\
& \quad && \exp(x) \le 100 \\
& \text{with} \quad && 0 \le x \le 10 \\
& \quad && y \in \{0, 1\}
\end{aligned}
Nonlinear expressions#
The renderer handles powers (superscripts), division (fractions), and the elementary functions (exp, log, sqrt, the trig family, …).
n = dm.Model("nlp")
a = n.continuous("a", lb=1, ub=5)
b = n.continuous("b", lb=1, ub=5)
n.maximize(dm.exp(a) / (b + 1))
n.subject_to(dm.sqrt(a) + b**2 <= 10)
n
nlp (2 variables, 1 constraint)Publication conventions#
to_latex() is meant to be pasted into a paper, so it follows the typographic conventions of mathematical writing rather than echoing the expression graph [International Organization for Standardization, 2019]:
products are implicit with the coefficient first and signs folded (
x - 2y, notx + -2 \cdot y);functions use their standard names (
\arcsin,\log_{2},\operatorname{erf},\max\{\cdot\},\lVert x \rVert_{2}), and composite atoms their textbook form (log1p(x)is \(\ln(1 + x)\),xlogx(x)is \(x \ln(x)\));identifiers are typeset as symbols:
alphais \(\alpha\),T_maxis \(T_{\mathrm{max}}\), multi-letter names are italic words;an indexed sum whose terms share one structure is recovered as \(\sum_{i=lo}^{hi}\), and a whole-array sum as \(\sum_{i}\). Recovery is exact: every term must match the template at its index, otherwise the terms are written out;
vector domains read \(x \in [0, 10]^{6}\), and constraints keep the variables on the left.
Numbers are printed exactly by default (the shortest decimal that round-trips to the stored value), because rounding a coefficient changes the constraint being shown. Pass precision= to round for readability, and standalone=True for a complete document that compiles with pdflatex.
import numpy as np
p = dm.Model("plant")
x = p.continuous("x", shape=(6,), lb=0, ub=10)
on = p.binary("on", shape=(6,))
c = p.parameter("c", value=np.arange(1.0, 7.0))
alpha = p.continuous("alpha", lb=0.1, ub=2)
T = p.continuous("T_max", lb=300, ub=600)
p.minimize(dm.sum(c * x) + 25 * dm.sum(on) - alpha * dm.log(T))
p.subject_to(dm.sum(x[k] ** 2 for k in range(6)) <= 50)
p.subject_to(dm.sum(x[k + 1] - x[k] for k in range(5)) >= -4)
p.subject_to(x[0] <= 10 * on[0])
p.subject_to((4 / 9) * T - dm.sin(x[3]) ** 2 / (1 + x[4]) <= 1)
p.subject_to(dm.maximum(x[0], x[1], x[2]) - dm.log1p(x[5]) <= 8)
p
plant (4 variables, 5 constraints)print(p.to_latex(precision=4))
\begin{aligned}
& \text{minimize} \quad && \sum_{i} c_{i} x_{i} + 25 \sum_{i} \mathit{on}_{i} - \alpha \ln(T_{\mathrm{max}}) \\
& \text{subject to} \quad && \sum_{i=0}^{5} x_{i}^{2} \le 50 \\
& \quad && \sum_{i=1}^{5} (x_{i} - x_{i-1}) \ge -4 \\
& \quad && x_{0} \le 10\,\mathit{on}_{0} \\
& \quad && 0.4444 T_{\mathrm{max}} - \frac{\sin^{2}(x_{3})}{1 + x_{4}} \le 1 \\
& \quad && \max\{x_{0}, x_{1}, x_{2}\} - \ln(1 + x_{5}) \le 8 \\
& \text{with} \quad && x \in [0, 10]^{6} \\
& \quad && \mathit{on} \in \{0, 1\}^{6} \\
& \quad && 0.1 \le \alpha \le 2 \\
& \quad && 300 \le T_{\mathrm{max}} \le 600
\end{aligned}
Large models are summarised#
Auto-display truncates large models so a notebook is never flooded: the first few constraints are shown, then a ⋮ row with the totals, and variables are summarised by type. Pass max_rows=None to to_latex() / to_html() for the complete form.
big = dm.Model("assignment")
z = [big.binary(f"z{i}") for i in range(40)]
big.minimize(sum((i + 1) * z[i] for i in range(40)))
for i in range(40):
big.subject_to(z[i] + z[(i + 1) % 40] <= 1)
big # truncated summary
assignment (40 variables, 40 constraints)Exporting#
to_latex() returns a LaTeX aligned block; to_html() returns a self-contained HTML fragment (the LaTeX typeset via MathJax) with a header naming the model. Both take max_rows to control truncation and precision to round printed decimals; to_latex(standalone=True) wraps the block in a minimal amsmath document. The plain-text repr / Model.summary() remain available for terminals and logging.
print(m.to_html()[:300], "...")
<div class="discopt-model"><div style="font-weight:600">Model <code>blend</code> <span style="color:#888;font-weight:400">(2 variables, 2 constraints)</span></div>$$
\begin{aligned}
& \text{minimize} \quad && (x - 3)^{2} + 2 y \\
& \text{subject to} \quad && x + 5 y \ge 4 \\
& \quad && \exp(x) \le ...