3.4. Variables¶
After you have checked Supported Problem Structure, the next modeling decision is the variable shape. For most first models, the useful order is:
choose scalar, vector, or matrix form
decide whether any sign or cone structure belongs to the variable itself
learn warm starts and reshaping only after the declaration pattern feels natural
Variable dimensions are limited to at most 2; higher-dimensional array variables are not supported.
3.4.1. Choose the Shape First¶
Start with the object you are trying to solve for:
use a scalar when the unknown is one number
use a vector when the unknown is a list of parallel coefficients
use a matrix when the unknown is naturally two-dimensional
Pattern |
Example |
Shape |
Typical use |
|---|---|---|---|
scalar |
|
|
single coefficient or intercept |
vector |
|
|
regression coefficients or allocations |
matrix |
|
|
linear maps, images, covariance objects |
x0 = admm.Var("offset")
x1 = admm.Var("direction", 2)
X = admm.Var("matrix", 3, 4)
Variable names are optional but strongly recommended for readability and model persistence.
3.4.2. Attributes vs. Explicit Constraints¶
Once the shape is right, decide whether a property is intrinsic to one variable or is just one model condition among many.
use a variable attribute when every feasible value of that variable must have the property by design
use
Model.addConstr()when the rule relates expressions, is easier to read as a separate line, or is only part of the final model logic
The library supports several structural attribute keywords directly in Var.__init__().
The active attributes can be inspected through Var.attr.
Mathematically, these attributes restrict the decision variable to a set such as
\(\mathbb{R}_+^n\), \(\mathbb{S}^n\), or \(\mathbb{S}_+^n\) before any additional constraints are
added.
Keyword |
Meaning |
Example |
|---|---|---|
|
Elementwise nonnegative variable |
|
|
Elementwise nonpositive variable |
|
|
Symmetric matrix |
|
|
Diagonal matrix structure |
|
|
Symmetric positive semidefinite matrix structure |
|
|
Symmetric negative semidefinite matrix structure |
|
|
Elementwise positive variable |
|
|
Elementwise negative variable |
|
For example, the statements
can often be modeled more directly by declaring x as nonnegative or X as PSD at creation time.
For PSD structure, two common forms are:
# Method 1: declare the variable itself as PSD
X = admm.Var("X", n, n, PSD=True)
# Method 2: create the matrix variable first, then add an explicit PSD constraint
X = admm.Var("X", n, n) # Unstructured matrix variable
model.addConstr(X >> 0) # PSD cone constraint
Use the first form when PSD is intrinsic to the variable. Use the second when you want PSD written as an explicit constraint or you are teaching the model through explicit feasibility conditions. For more on the constraint side of that choice, see Constraints.
3.4.3. Accessing Solution Values¶
After calling Model.optimize(), you can retrieve the optimal solution from a variable using the
Var.X attribute. This returns a NumPy array containing the numerical values.
import admm
import numpy as np
# Create and solve a simple model
model = admm.Model()
x = admm.Var("x", 5)
model.setObjective(admm.sum(admm.square(x - 1)))
model.optimize()
# Access the solution
solution = x.X
print(f"Optimal solution: {solution}")
print(f"Type: {type(solution)}") # <class 'numpy.ndarray'>
print(f"Shape: {solution.shape}") # (5,)
For matrix variables, Var.X returns a 2D NumPy array:
X = admm.Var("X", 3, 3, PSD=True)
model.optimize()
# Access the matrix solution
X_optimal = X.X
print(f"Matrix shape: {X_optimal.shape}") # (3, 3)
Note
The Var.X attribute is only populated after a successful optimization call.
Before calling Model.optimize(), accessing Var.X will return None.
Always check the solver status before using the solution:
model.optimize()
if model.StatusString == "SOLVE_OPT_SUCCESS":
print(f"Solution: {x.X}")
else:
print(f"Optimization failed: {model.StatusString}")
3.4.4. Warm Start¶
Once variable creation feels routine, the next convenience to learn is warm start. A variable can be given a
starting point via Var.start, which is useful when you already have a good approximate solution
from a previous run or an external heuristic.
x = admm.Var("x", n)
x.start = initial_guess
model.optimize()
3.4.5. Basic Operators¶
After declaration, variables behave like symbolic scalar, vector, and matrix objects. The operators below cover most day-to-day modeling syntax.
Operator |
Meaning |
Example |
NumPy analogue |
|---|---|---|---|
|
Elementwise addition |
|
|
|
Elementwise subtraction |
|
|
|
Elementwise multiplication |
|
|
|
Elementwise division |
|
|
|
Matrix multiplication |
|
|
|
Elementwise power |
|
|
Broadcasting follows NumPy rules when shapes are compatible.
3.4.6. Variable Shapes and Transformations¶
When you start writing block constraints or matrix reformulations, these shape tools are the next ones to reach for.
Tool |
Example |
Output effect |
|---|---|---|
|
returns dimensions |
|
|
returns rank |
|
|
transpose |
|
|
reindex without changing element count |
|
slicing |
|
subobject selection |
X = admm.Var("X", 2, 3)
XT = X.T
left_block = X[:, 0:2]
first_column = X[:, 0]
reshaped = X.reshape(3, 2)
These transformations follow NumPy-style conventions and are often the cleanest way to express block constraints or matrix-to-vector reformulations.