CA_MINIMIZE
Overview
The CA_MINIMIZE function solves nonlinear programming (NLP) problems using CasADi, an open-source framework for numerical optimization with automatic differentiation (AD). CasADi is widely used in academic and industrial settings for gradient-based optimization, particularly in optimal control applications. For details, see the CasADi documentation.
This function formulates and solves problems of the form:
\begin{aligned}
\text{minimize} \quad & f(x) \\
\text{subject to} \quad & g_{lb} \leq g(x) \leq g_{ub} \\
& x_{lb} \leq x \leq x_{ub}
\end{aligned}
where f(x) is the objective function, g(x) represents constraints, and x is the vector of decision variables. The function parses symbolic expressions (e.g., x[0]^2 + x[1]^2) and constructs a computational graph that CasADi uses for efficient derivative calculation via AD. This enables the solver to compute exact gradients and Hessians without numerical approximation.
The default solver is SQP (Sequential Quadratic Programming), accessed via CasADi’s sqpmethod plugin. SQP iteratively approximates the NLP as a sequence of quadratic subproblems, converging to a local minimum. The method requires a good initial guess (x_zero) and works best for smooth, well-scaled problems.
The function supports equality constraints (specified with ==) and inequality constraints (specified with <=), as well as box bounds on decision variables. Constraint bounds are provided as [lower, upper] pairs, where setting lower == upper enforces equality.
This implementation leverages CasADi’s SX symbolic type for constructing expressions and supports common mathematical operations including trigonometric functions (sin, cos, tan), exponentials (exp, log), and power functions. The source code is available on the CasADi GitHub repository.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=CA_MINIMIZE(func_expr, x_zero, constraint_exprs, constraint_types, constraint_bounds, bounds, solver)
func_expr(str, required): The objective function expression written in terms of x, e.g. “x[0]2 + x[1]2”.x_zero(list[list], required): Initial guess for the optimization variables as a 2D list, e.g. [[1, 1]].constraint_exprs(list[list], optional, default: null): List of constraint expression strings in terms of x.constraint_types(list[list], optional, default: null): List of constraint types, either “<=” for inequality or “==” for equality.constraint_bounds(list[list], optional, default: null): Bounds for each constraint as [[lower, upper], …].bounds(list[list], optional, default: null): Bounds for each decision variable as [[lower, upper], …].solver(str, optional, default: “sqpmethod”): Name of the optimization solver to use.
Returns (list[list]): 2D list [[x1, x2, …, objective]], or error message string.
Examples
Example 1: Demo case 1
Inputs:
| func_expr | x_zero | |
|---|---|---|
| x[0]^2 + x[1]^2 | 1 | 1 |
Excel formula:
=CA_MINIMIZE("x[0]^2 + x[1]^2", {1,1})
Expected output:
| Result | ||
|---|---|---|
| 0 | 0 | 0 |
Example 2: Demo case 2
Inputs:
| func_expr | x_zero | bounds | ||
|---|---|---|---|---|
| (x[0]-3)^2 + (x[1]-4)^2 + 7 | 1 | 1 | 0 | 10 |
| 0 | 10 |
Excel formula:
=CA_MINIMIZE("(x[0]-3)^2 + (x[1]-4)^2 + 7", {1,1}, {0,10;0,10})
Expected output:
| Result | ||
|---|---|---|
| 3 | 4 | 7 |
Example 3: Demo case 3
Inputs:
| func_expr | x_zero | constraint_exprs | constraint_types | constraint_bounds | ||
|---|---|---|---|---|---|---|
| x[0]^2 + x[1]^2 | 1 | 1 | x[0] + x[1] | == | 2 | 2 |
Excel formula:
=CA_MINIMIZE("x[0]^2 + x[1]^2", {1,1}, x[0] + x[1], ==, {2,2})
Expected output:
| Result | ||
|---|---|---|
| 1 | 1 | 2 |
Example 4: Demo case 4
Inputs:
| func_expr | x_zero | bounds | ||
|---|---|---|---|---|
| (1 - x[0])^2 + 100*(x[1] - x[0]2)2 | -1 | 2 | -2 | 2 |
| -2 | 2 |
Excel formula:
=CA_MINIMIZE("(1 - x[0])^2 + 100*(x[1] - x[0]^2)^2", {-1,2}, {-2,2;-2,2})
Expected output:
| Result | ||
|---|---|---|
| 1 | 1 | 0 |
Python Code
import re
import math
import numpy as np
import casadi as ca
def ca_minimize(func_expr, x_zero, constraint_exprs=None, constraint_types=None, constraint_bounds=None, bounds=None, solver='sqpmethod'):
"""
Minimize a multivariate function using CasADi with automatic differentiation.
See: https://web.casadi.org/docs/
This example function is provided as-is without any representation of accuracy.
Args:
func_expr (str): The objective function expression written in terms of x, e.g. "x[0]**2 + x[1]**2".
x_zero (list[list]): Initial guess for the optimization variables as a 2D list, e.g. [[1, 1]].
constraint_exprs (list[list], optional): List of constraint expression strings in terms of x. Default is None.
constraint_types (list[list], optional): List of constraint types, either "<=" for inequality or "==" for equality. Default is None.
constraint_bounds (list[list], optional): Bounds for each constraint as [[lower, upper], ...]. Default is None.
bounds (list[list], optional): Bounds for each decision variable as [[lower, upper], ...]. Default is None.
solver (str, optional): Name of the optimization solver to use. Default is 'sqpmethod'.
Returns:
list[list]: 2D list [[x1, x2, ..., objective]], or error message string.
"""
def to2d(v):
return [[v]] if not isinstance(v, list) else v
if not isinstance(func_expr, str) or not func_expr.strip():
return "Invalid input: func_expr must be a non-empty string."
if not re.search(r'\bx\b', func_expr):
return "Invalid input: function expression must contain the variable 'x'."
func_expr = re.sub(r'\^', '**', func_expr)
x_zero = to2d(x_zero)
if not isinstance(x_zero, list) or len(x_zero) == 0 or not isinstance(x_zero[0], list):
return "Invalid input: x_zero must be a 2D list, e.g., [[0, 0]]."
x0_row = x_zero[0]
if len(x0_row) == 0:
return "Invalid input: x_zero must contain at least one variable."
try:
x0_vector = np.asarray([float(v) for v in x0_row], dtype=float)
except (TypeError, ValueError):
return "Invalid input: x_zero must contain numeric values."
num_vars = x0_vector.size
try:
x = ca.SX.sym('x', num_vars)
except Exception:
return "Error: Could not create symbolic variables."
def parse_expr(e_str, variables):
ns = {
'x': variables, 'sin': ca.sin, 'cos': ca.cos, 'tan': ca.tan,
'asin': ca.asin, 'arcsin': ca.arcsin, 'acos': ca.acos,
'arccos': ca.arccos, 'atan': ca.atan, 'arctan': ca.arctan,
'sinh': ca.sinh, 'cosh': ca.cosh, 'tanh': ca.tanh,
'exp': ca.exp, 'log': ca.log, 'ln': ca.log, 'log10': ca.log10,
'sqrt': ca.sqrt, 'abs': ca.fabs, 'pow': ca.power,
'pi': math.pi, 'e': math.e,
}
try:
return eval(e_str, {"__builtins__": {}}, ns), None
except Exception as exc:
return None, str(exc)
obj_expr, obj_err = parse_expr(func_expr, x)
if obj_err:
return f"Error: Invalid objective expression: {obj_err}"
if obj_expr is None:
return "Error: Could not parse objective expression."
c_list = []
c_lower = []
c_upper = []
if constraint_exprs is not None:
constraint_exprs = to2d(constraint_exprs)
if isinstance(constraint_exprs[0], list):
c_exprs = [e for row in constraint_exprs for e in row]
else:
c_exprs = constraint_exprs
c_types = to2d(constraint_types) if constraint_types else None
if c_types and isinstance(c_types[0], list):
c_types = [t for row in c_types for t in row]
c_bnds = to2d(constraint_bounds) if constraint_bounds else None
for i, ce in enumerate(c_exprs):
ce_str = re.sub(r'\^', '**', str(ce))
c_parsed, c_err = parse_expr(ce_str, x)
if c_err:
return f"Error: Invalid constraint expression {i}: {c_err}"
if c_parsed is None:
return f"Error: Could not parse constraint expression {i}."
c_list.append(c_parsed)
if c_bnds and i < len(c_bnds):
bp = c_bnds[i]
if isinstance(bp, list) and len(bp) >= 2:
try:
c_lower.append(float(bp[0]) if bp[0] is not None else -float('inf'))
c_upper.append(float(bp[1]) if bp[1] is not None else float('inf'))
except (TypeError, ValueError):
return f"Error: Invalid bounds for constraint {i}."
else:
c_lower.append(-float('inf'))
c_upper.append(float('inf'))
else:
c_lower.append(-float('inf'))
c_upper.append(float('inf'))
x_lower = [-float('inf')] * num_vars
x_upper = [float('inf')] * num_vars
if bounds is not None:
bounds = to2d(bounds)
if len(bounds) != num_vars:
return "Invalid input: bounds must provide one [min, max] pair per variable."
for i, bp in enumerate(bounds):
if isinstance(bp, list) and len(bp) >= 2:
try:
x_lower[i] = float(bp[0]) if bp[0] is not None else -float('inf')
x_upper[i] = float(bp[1]) if bp[1] is not None else float('inf')
except (TypeError, ValueError):
return f"Invalid input: bounds for variable {i + 1} must be numeric or None."
if x_lower[i] > x_upper[i]:
return f"Invalid input: lower bound cannot exceed upper bound for variable {i + 1}."
else:
return "Invalid input: each bounds entry must be a [min, max] pair."
try:
nlp = {'x': x, 'f': obj_expr}
if c_list:
nlp['g'] = ca.vertcat(*c_list)
opts = {
'qpsol': 'qrqp',
'qpsol_options': {'print_iter': False, 'print_header': False},
'print_time': 0
}
S = ca.nlpsol('S', solver, nlp, opts)
sol_in = {'x0': x0_vector, 'lbx': x_lower, 'ubx': x_upper}
if c_list:
sol_in['lbg'] = c_lower
sol_in['ubg'] = c_upper
sol = S(**sol_in)
x_opt = sol['x'].full().flatten()
f_opt = float(sol['f'])
if not all(math.isfinite(v) for v in x_opt):
return "ca_minimize failed: solution contains non-finite values."
if not math.isfinite(f_opt):
return "ca_minimize failed: objective value is not finite."
return [[float(v) for v in x_opt] + [f_opt]]
except Exception as exc:
return f"ca_minimize error: {str(exc)}"