This tutorial covers advanced constraint techniques:
Different constraint types
Modeling tricks
Handling infeasibility
Constraint debugging
2 Constraint Types
2.1 Inequality Constraints
The most common type—limit something to be above or below a threshold:
from optyx import Variablex = Variable("x")y = Variable("y")# Greater-than-or-equalc1 = x + y >=10# x + y ≥ 10# Less-than-or-equalc2 = x**2+ y**2<=25# x² + y² ≤ 25print(f"c1 sense: {c1.sense}")print(f"c2 sense: {c2.sense}")
c1 sense: >=
c2 sense: <=
2.2 Equality Constraints
When something must be exactly equal:
from optyx import Variablex = Variable("x")y = Variable("y")# Use .eq() for equalityc = (x + y).eq(1) # x + y = 1print(f"Equality sense: {c.sense}")
Equality sense: ==
Important
Don’t use == for constraints! Python’s == returns a boolean, not a constraint. Always use .eq().
3 Bound Constraints
For simple variable bounds, prefer using variable bounds over explicit constraints:
from optyx import Variable, Problem# Preferred: bounds on variable definitionx = Variable("x", lb=0, ub=10)# Less efficient: as explicit constraintsy = Variable("y")prob = ( Problem() .minimize(x**2+ y**2) .subject_to(y >=0) .subject_to(y <=10) .solve())print(f"x* = {prob['x']:.2f}, y* = {prob['y']:.2f}")
x* = 0.00, y* = 0.00
Why prefer variable bounds? - Solver handles them more efficiently - Cleaner problem formulation - Fewer constraint evaluations
4 Modeling Techniques
4.1 Sum Constraints
Ensure quantities add up:
from optyx import Variable, Problem# Portfolio weightsw1 = Variable("stocks", lb=0, ub=1)w2 = Variable("bonds", lb=0, ub=1)w3 = Variable("cash", lb=0, ub=1)# Weights must sum to 1 (with small tolerance for numerical stability)sol = ( Problem() .minimize(w1**2+ w2**2+ w3**2) # Minimize concentration .subject_to(w1 + w2 + w3 >=0.99) .subject_to(w1 + w2 + w3 <=1.01) .solve())total = sol['stocks'] + sol['bonds'] + sol['cash']print(f"Sum of weights: {total:.4f}")
Sum of weights: 0.9900
4.2 Ratio Constraints
Control proportions:
from optyx import Variable, Problemx = Variable("x", lb=0.1) # Avoid division by zeroy = Variable("y", lb=0.1)# y should be at least twice x# y/x >= 2 → y >= 2xsol = ( Problem() .minimize(x + y) .subject_to(y >=2*x) .subject_to(x + y >=10) .solve())print(f"x = {sol['x']:.2f}, y = {sol['y']:.2f}")print(f"Ratio y/x = {sol['y']/sol['x']:.2f}")
x = 0.10, y = 9.90
Ratio y/x = 99.00
4.3 Nonlinear Constraints
Optyx handles nonlinear constraints:
from optyx import Variable, Problem, sqrtx = Variable("x")y = Variable("y")# Point must be inside a circlesol = ( Problem() .minimize((x -3)**2+ (y -4)**2) # Closest to (3, 4) .subject_to(x**2+ y**2<=4) # Inside circle of radius 2 .solve())dist_from_origin = sqrt(sol['x']**2+ sol['y']**2).evaluate({'x': sol['x'], 'y': sol['y']})print(f"Point: ({sol['x']:.3f}, {sol['y']:.3f})")print(f"Distance from origin: {dist_from_origin:.3f}")
Point: (1.200, 1.600)
Distance from origin: 2.000
5 Handling Infeasibility
5.1 Detecting Infeasible Problems
from optyx import Variable, Problem, SolverStatusx = Variable("x", lb=0, ub=5)# Impossible: x >= 10 but x <= 5sol = ( Problem() .minimize(x) .subject_to(x >=10) .solve())print(f"Status: {sol.status}")if sol.status != SolverStatus.OPTIMAL:print(f"Problem: {sol.message}")
Status: SolverStatus.INFEASIBLE
Problem: The problem is infeasible. (HiGHS Status 8: model_status is Infeasible; primal_status is None) No feasible solution exists.
5.2 Soft Constraints with Penalties
When hard constraints might be infeasible, use penalties:
from optyx import Variable, Problemx = Variable("x", lb=0)slack = Variable("slack", lb=0) # Violation amount# Original constraint: x >= 10# Soft version: x + slack >= 10, penalize slacksol = ( Problem() .minimize(x**2+1000*slack) # Large penalty for violation .subject_to(x + slack >=10) .subject_to(x <=5) # Conflicting constraint .solve())print(f"x = {sol['x']:.2f}")print(f"Constraint violation: {sol['slack']:.2f}")
x = 5.00
Constraint violation: 5.00
6 Multiple Constraints
6.1 Building Constraint Lists
from optyx import Variable, Problemimport numpy as npn =3x = np.array([Variable(f"x_{i}", lb=0) for i inrange(n)])# Resource limitslimits = np.array([100, 80, 60])usage = np.array([[2, 1, 3], [1, 2, 1], [1, 1, 2]])prob = Problem().maximize(np.sum(10* x))# Add resource constraints using matrix notationfor j inrange(len(limits)): prob.subject_to(usage[j] @ x <= limits[j])sol = prob.solve()for i inrange(n):print(f"x_{i} = {sol[f'x_{i}']:.2f}")
x_0 = 40.00
x_1 = 20.00
x_2 = 0.00
TipNumPy Matrix Operations
Optyx variables work seamlessly with NumPy arrays. Use np.array([...]) to wrap your variables, then use @ for matrix multiplication and np.sum() for summations.
6.2 Constraint Generators
For large problems, generate constraints programmatically:
from optyx import Variable, Problem# Grid of variablesrows, cols =3, 3grid = [[Variable(f"x_{i}_{j}", lb=0, ub=9) for j inrange(cols)] for i inrange(rows)]prob = Problem().minimize(sum(grid[i][j] for i inrange(rows) for j inrange(cols)))# Row sums >= 10for i inrange(rows): prob.subject_to(sum(grid[i][j] for j inrange(cols)) >=10)# Column sums >= 10for j inrange(cols): prob.subject_to(sum(grid[i][j] for i inrange(rows)) >=10)sol = prob.solve()print("Grid solution:")for i inrange(rows): row = [f"{sol[f'x_{i}_{j}']:.1f}"for j inrange(cols)]print(f" {row}")