CasADi expressions¶
The model callback is traced once when System is built. Arguments
t, x, u, and (if used) z and p are CasADi MX symbols, not
Python floats or NumPy arrays. IPOPT later evaluates that graph.
math.exp and np.exp do not work on those symbols.
After solve, sol.time, sol.x, sol.u are NumPy. Plotting
and post-processing use NumPy and matplotlib
(Results and plots).
import casadi as cs
import eco_opt
from eco_opt import Namespace
k = 0.3
def model(t, x, u):
stock, harvest = x[0], u[0]
# stock is MX: cs.exp, not np.exp or math.exp
loss = k * stock * cs.exp(-stock)
y = Namespace(stock=stock, harvest=harvest, loss=loss)
return -harvest - loss, y
Python +, -, *, /, ** on MX mixed with Python floats are
valid. Return a Python list of derivatives; System stacks it to
match states. A one-state model may return a scalar.
Numeric bounds in constraint tuples stay Python floats
(0, 1.0 / 9.0, float("inf")). Expressions in the model, in
ocp.max / ocp.min, and in constraint expressions are CasADi.
A Python if on a numeric constant (a float you pass in, not
sys.p) is evaluated before the graph exists. A Python if on
t, x, u, z, or p is invalid
(Transcription).
Import casadi as cs only when the right-hand side needs a function
below. Many one-state models need only +, -, *, /.
Arithmetic¶
| Add, subtract, multiply, divide | a + b, a - b, a * b, a / b |
| Power | a ** b or cs.power(a, b) |
| Square | a ** 2 |
| Unary minus | -a |
Do not stack \(\dot{\mathbf{x}}\) with cs.vertcat. Return
[dx1, dx2, ...].
Elementary¶
| Exponential | cs.exp(x) |
| Natural log | cs.log(x) |
| Base-10 log | cs.log10(x) |
| Square root | cs.sqrt(x) |
| Absolute value | cs.fabs(x) |
| Sign (\(-1,0,+1\)) | cs.sign(x) |
| Floor, ceiling | cs.floor(x), cs.ceil(x) |
| Remainder | cs.fmod(x, y) |
cs.exp and cs.sign appear in Goddard; cs.log in King allocation,
shallow-lake \(J\), and a Lenhart lab; cs.sqrt in the Iwasa smooth
minimum and quadratic QSS.
Floor, ceiling, and fmod are stepwise. Prefer a smooth surrogate if
IPOPT’s Hessian must exist (see below).
Trigonometric and hyperbolic¶
| Sine, cosine, tangent | cs.sin, cs.cos, cs.tan |
| Inverse | cs.asin, cs.acos, cs.atan, cs.atan2(y, x) |
| Hyperbolic | cs.sinh, cs.cosh, cs.tanh |
| Inverse hyperbolic | cs.asinh, cs.acosh, cs.atanh |
| \(\pi\) | cs.pi |
cs.sin / cs.cos appear in the brachistochrone, isoperimetric
example, and irrigation ET. cs.tanh is a useful smooth step even
though no shipped example uses it.
Min, max, error function¶
| Minimum, maximum | cs.fmin(a, b), cs.fmax(a, b) |
| Error function | cs.erf(x) |
| Inverse error function | cs.erfinv(x) |
cs.fmin / cs.fmax are nondifferentiable on a set of positive
measure. Lindh uses cs.fmax(V, 1e-9) as a guard; lignin uses
cs.fmin on a numeric efficiency cap (evaluated before the
graph). If IPOPT stalls on a min / max of states, replace it
with a smooth surrogate. Shoot–root
uses
in place of \(\min(a,b)\). A smooth step is 0.5 * (1 + cs.tanh(x / eps)).
Branching and logic¶
A switch that depends on \(t\), \(\mathbf{x}\), or \(\mathbf{u}\) is
cs.if_else, not a Python if.
| Switch | cs.if_else(cond, a, b) |
| And / or / not | cs.logic_and, cs.logic_or, cs.logic_not |
| Comparisons | t >= t0, x[0] > 0 (overloaded; use inside if_else) |
| \(\pm\infty\) in tuples | float("inf") (or cs.inf) |
alive = cs.if_else(t >= t_cut, 0.0, 1.0)
rate = alive * vmax * x[0] / (km + x[0])
on = cs.logic_and(t >= t0, t < tf)
I = I0 * cs.if_else(on, 1.0, 0.0)
Pulse forcing: non_diff, irrigation,
microbial and lignin open_vs_closed. Nested if_else in
root_opt selects mineralisation vs
immobilisation.
Tabulated forcing¶
Weather, light, or a prescribed input that is a known function of time is not a control. Store it as a CasADi interpolant of \(t\):
I_fun = cs.interpolant("I_light", "linear", [t_grid], I_tab)
I = I_fun(t)
See greenhouse. Do not call np.interp
inside the model.
Linear algebra (rarely needed)¶
Process ODEs are almost always scalar or a short list of
scalars. If a term is genuinely a matrix expression, CasADi also
provides cs.mtimes, cs.inv, cs.solve, cs.dot, cs.norm_2,
cs.sum1, cs.transpose. Prefer a Python list of scalar rates
over building one large MX column yourself.
Reference¶
CasADi’s own list of operations: web.casadi.org/docs.