Nonlinear Terms¶
This guide covers LumiX’s nonlinear modeling capabilities and automatic linearization.
Introduction¶
Real-world optimization problems often involve nonlinear relationships:
Absolute deviations from targets
Products of decision variables (e.g., price × quantity)
Min/max operations over alternatives
Conditional constraints (if-then logic)
Complex nonlinear functions (exponential, logarithmic, etc.)
Challenge: Most practical solvers only handle linear constraints.
Solution: LumiX provides nonlinear term definitions that are automatically linearized into equivalent linear formulations compatible with MIP solvers.
Philosophy¶
Declarative Nonlinear Modeling¶
Instead of manually creating linearization constraints, you declare what you want:
# Traditional approach - manual linearization
z = model.addVar()
model.addConstr(z >= x)
model.addConstr(z >= -x)
# Remember to use z instead of |x| everywhere...
# LumiX approach - declarative
abs_x = LXAbsoluteTerm(var=x)
# Linearization happens automatically!
Benefits:
More readable and maintainable code
Less error-prone
Optimal linearization method selected automatically
Easy to experiment with different formulations
Automatic Linearization¶
The linearization happens transparently when you build the model:
sequenceDiagram
participant User
participant NonlinearTerm
participant Linearizer
participant Model
participant Solver
User->>NonlinearTerm: Create LXBilinearTerm(x, y)
User->>Model: Add constraints with term
User->>Solver: solve(model)
Solver->>Linearizer: Linearize terms
Linearizer->>NonlinearTerm: Analyze variable types
Linearizer->>Model: Add auxiliary vars & constraints
Model-->>Solver: Linear model
Solver-->>User: Solution
Core Components¶
LumiX provides five nonlinear term types:
graph LR
A[Nonlinear Terms] --> B[LXAbsoluteTerm]
A --> C[LXMinMaxTerm]
A --> D[LXBilinearTerm]
A --> E[LXIndicatorTerm]
A --> F[LXPiecewiseLinearTerm]
style A fill:#e1f5ff
style B fill:#fff4e1
style C fill:#ffe1e1
style D fill:#e1ffe1
style E fill:#f0e1ff
style F fill:#ffe8e1
Absolute Value (
LXAbsoluteTerm)Represents x for minimizing deviations or handling penalties.
Min/Max (
LXMinMaxTerm)Minimum or maximum over multiple variables.
Bilinear Products (
LXBilinearTerm)Products of two variables (x * y), automatically linearized based on types.
Indicator Constraints (
LXIndicatorTerm)Conditional constraints: “if binary_var then constraint holds”.
Piecewise-Linear (
LXPiecewiseLinearTerm)Approximate arbitrary nonlinear functions with piecewise-linear segments.
Quick Start Example¶
Here’s a complete example using multiple nonlinear terms:
from dataclasses import dataclass
from lumix import LXModel, LXVariable, LXConstraint
from lumix.nonlinear import LXBilinearTerm, LXAbsoluteTerm, LXIndicatorTerm
@dataclass
class Facility:
id: str
capacity: float
fixed_cost: float
@dataclass
class Product:
id: str
demand: float
target: float
facilities = [...] # Your data
products = [...]
# Binary: is facility open?
is_open = (
LXVariable[Facility, int]("is_open")
.binary()
.indexed_by(lambda f: f.id)
.from_data(facilities)
)
# Continuous: production quantity
production = (
LXVariable[Product, float]("production")
.continuous()
.bounds(lower=0)
.indexed_by(lambda p: p.id)
.from_data(products)
)
# Nonlinear: actual_production = is_open * production
# (only produce if facility is open)
actual_production = LXBilinearTerm(
var1=is_open,
var2=production
)
# Nonlinear: minimize absolute deviation from target
deviation = LXAbsoluteTerm(var=production, coefficient=1.0)
# Nonlinear: if facility is open, must meet minimum capacity
min_capacity = LXIndicatorTerm(
binary_var=is_open,
condition=True,
linear_expr=production_expr, # Define this expression
sense='>=',
rhs=100.0
)
# Build model (linearization happens automatically)
model = LXModel("facility_planning")
# ... add constraints and objective ...
Integration with Linearization Module¶
The nonlinear terms work seamlessly with the linearization module:
from lumix.linearization import LXLinearizer, LXLinearizerConfig
from lumix.linearization import LXLinearizationMethod
# Configure linearization (optional - defaults are smart)
config = LXLinearizerConfig(
bilinear_method=LXLinearizationMethod.MCCORMICK,
piecewise_segments=30,
auto_detect_bounds=True
)
# Linearization happens automatically during model solve
linearizer = LXLinearizer(config=config)
linear_model = linearizer.linearize(model)
See Linearization Concepts for details on linearization configuration.
Component Guides¶
Dive deeper into each nonlinear term type:
Type Safety and IDE Support¶
All nonlinear terms are fully type-annotated dataclasses:
from lumix.nonlinear import LXBilinearTerm
# Full IDE autocomplete and type checking
bilinear = LXBilinearTerm(
var1=price, # Type: LXVariable
var2=quantity, # Type: LXVariable
coefficient=0.9 # Type: float
)
# Type errors caught at development time
bilinear = LXBilinearTerm(
var1="not_a_variable" # ✗ Type error!
)
Linearization Methods¶
Different nonlinear terms use different linearization techniques:
Term Type |
Linearization Method |
Key Parameters |
|---|---|---|
Absolute Value |
Auxiliary variable + constraints |
None |
Min/Max |
Auxiliary variable + bounding constraints |
None |
Bilinear (Binary × Binary) |
AND logic |
None |
Bilinear (Binary × Continuous) |
Big-M |
M value (auto-computed) |
Bilinear (Continuous × Continuous) |
McCormick envelopes |
Variable bounds (required) |
Indicator |
Big-M |
M value (auto-computed) |
Piecewise-Linear |
SOS2, Incremental, or Logarithmic |
Segments, method, adaptive |
See Nonlinear Module Architecture for implementation details.
Best Practices¶
Variable Bounds¶
Always define bounds for continuous variables used in nonlinear terms:
# Good - bounds defined
price = LXVariable[Product, float]("price").continuous().bounds(10, 100)
quantity = LXVariable[Product, float]("qty").continuous().bounds(0, 1000)
revenue = LXBilinearTerm(var1=price, var2=quantity)
# Bad - no bounds (linearization may fail)
price = LXVariable[Product, float]("price").continuous()
revenue = LXBilinearTerm(var1=price, var2=quantity) # ✗ Error!
Big-M Selection¶
For Big-M linearizations (indicator constraints, binary × continuous), tighter bounds lead to better performance:
# Tight bounds → smaller M → better numerics
flow = LXVariable[Route, float]("flow").continuous().bounds(0, 500)
# Loose bounds → larger M → numerical issues
flow = LXVariable[Route, float]("flow").continuous().bounds(0, 1e9) # ✗ Avoid
Piecewise Segments¶
Balance accuracy vs. model size:
# More segments = better accuracy but slower
exp_term = LXPiecewiseLinearTerm(
var=time,
func=lambda t: math.exp(t),
num_segments=50 # High accuracy
)
# Fewer segments = faster but less accurate
exp_term = LXPiecewiseLinearTerm(
var=time,
func=lambda t: math.exp(t),
num_segments=10 # Fast, may be sufficient
)
Use adaptive segmentation for non-uniform functions:
# Adaptive places more segments where function curves sharply
sigmoid_approx = LXPiecewiseLinearTerm(
var=x,
func=lambda x: 1 / (1 + math.exp(-x)),
num_segments=30,
adaptive=True # More segments near inflection point
)
Next Steps¶
Learn each term type:
Absolute Value Terms - Absolute value operations and deviation minimization
Min/Max Operations - Min/max operations over alternatives
Bilinear Products - Products of variables and automatic linearization
Indicator Constraints - Conditional constraints and if-then logic
Piecewise-Linear Functions - Approximating arbitrary nonlinear functions
Advanced topics:
Linearization Concepts - Linearization configuration and methods
Nonlinear Module Architecture - Implementation details
Extending Nonlinear Module - Adding custom nonlinear terms
Related modules:
Core Concepts - Core modeling concepts
Nonlinear Module API - Complete API reference