Advanced Solver Features¶
This guide covers advanced solver features like warm start, callbacks, and sensitivity analysis.
Overview¶
Advanced features available in LumiX solvers:
Warm Start: Use previous solution to speed up solving
Sensitivity Analysis: Shadow prices and reduced costs
Callbacks: Custom heuristics and lazy constraints (Gurobi/CPLEX only)
Solution Pools: Multiple solutions for MIP (Gurobi/CPLEX only)
IIS/Conflict Refinement: Debug infeasible models (Gurobi/CPLEX only)
Note
Not all solvers support all features. Check Solver Capabilities for details.
Warm Start¶
Warm start uses a previous solution as a starting point for solving, which can significantly speed up solving for similar models.
When to Use¶
Good for:
Solving a sequence of similar models
Re-optimizing after small parameter changes
Interactive optimization (user tweaks and re-solves)
Rolling horizon planning
Not useful for:
First solve (no previous solution)
Completely different models
Major structural changes
Basic Usage¶
Currently, warm start is primarily used internally by solvers when you solve similar models sequentially. LumiX automatically leverages this when applicable.
from lumix import LXOptimizer
optimizer = LXOptimizer().use_solver("gurobi")
# First solve
solution1 = optimizer.solve(model1)
# Modify model slightly
model2 = model1.copy()
model2.update_parameter("demand", new_demand)
# Second solve may benefit from internal warm start
solution2 = optimizer.solve(model2)
Supported Solvers¶
Solver |
Supported |
Notes |
|---|---|---|
OR-Tools |
✓ |
Automatic when solving sequentially |
Gurobi |
✓ |
Automatic MIP start |
CPLEX |
✓ |
Automatic warm start |
GLPK |
✗ |
Not supported |
CP-SAT |
✓ |
Via solution hints |
Sensitivity Analysis¶
Sensitivity analysis provides information about how the optimal solution changes with small changes to the model.
What is Sensitivity Analysis?¶
Shadow Prices (dual values):
How much the objective would improve if a constraint’s RHS increased by 1 unit
Only meaningful for linear programs (or LP relaxation)
Reduced Costs:
How much the objective coefficient would need to change before a variable becomes positive
Indicates “opportunity cost” of forcing a variable to be non-zero
Enabling Sensitivity¶
from lumix import LXOptimizer
optimizer = LXOptimizer().use_solver("gurobi").enable_sensitivity()
solution = optimizer.solve(model)
# Access sensitivity information
shadow_prices = solution.shadow_prices
reduced_costs = solution.reduced_costs
Accessing Shadow Prices¶
# Get shadow price for a constraint
capacity_constraint = model.get_constraint("capacity")
for resource in resources:
shadow_price = solution.get_shadow_price(capacity_constraint, resource)
print(f"{resource.name}: ${shadow_price:.2f} per unit")
Interpretation:
Shadow price of $5 means: increasing capacity by 1 unit would increase profit by $5
Shadow price of $0 means: constraint is not binding (has slack)
Negative shadow price: relaxing constraint decreases objective (for maximization)
Accessing Reduced Costs¶
# Get reduced cost for a variable
production = model.get_variable("production")
for product in products:
reduced_cost = solution.get_reduced_cost(production, product)
if reduced_cost > 0:
print(f"{product.name}: would need ${reduced_cost:.2f} better profit to produce")
Interpretation:
Reduced cost of $10 means: profit would need to increase by $10 before producing this product
Reduced cost of $0 means: variable is in the optimal basis (positive in solution)
Example: Resource Valuation¶
from lumix import LXModel, LXOptimizer
# Build production model
model = build_production_model(products, resources)
# Enable sensitivity analysis
optimizer = LXOptimizer().use_solver("gurobi").enable_sensitivity()
solution = optimizer.solve(model)
# Evaluate resources
print("Resource Shadow Prices:")
capacity_constraint = model.get_constraint("capacity")
for resource in resources:
shadow_price = solution.get_shadow_price(capacity_constraint, resource)
current_capacity = resource.capacity
print(f"\n{resource.name}:")
print(f" Current capacity: {current_capacity}")
print(f" Shadow price: ${shadow_price:.2f}")
print(f" Value of +10 units: ${shadow_price * 10:.2f}")
# Decision: Should we buy more capacity?
cost_per_unit = resource.expansion_cost
if shadow_price > cost_per_unit:
print(f" ✓ RECOMMEND: Expand (value ${shadow_price:.2f} > cost ${cost_per_unit:.2f})")
else:
print(f" ✗ Don't expand (value ${shadow_price:.2f} < cost ${cost_per_unit:.2f})")
Supported Solvers¶
Solver |
Supported |
Notes |
|---|---|---|
OR-Tools |
✗ |
Not available |
Gurobi |
✓ |
Full support (LP and MIP) |
CPLEX |
✓ |
Full support (LP and MIP) |
GLPK |
✓ |
LP only |
CP-SAT |
✗ |
Not available |
Callbacks (Gurobi/CPLEX Only)¶
Callbacks allow you to inject custom logic during the solving process.
Note
Callbacks are an advanced feature. Most users don’t need them.
Types of Callbacks¶
Lazy Constraints: Add constraints only when violated
User Cuts: Add cutting planes to improve bounds
Custom Heuristics: Generate feasible solutions during search
Information Callbacks: Monitor solve progress
When to Use¶
Lazy Constraints:
Model has exponentially many constraints
Can’t enumerate all constraints upfront
Example: TSP subtour elimination
User Cuts:
Know valid cutting planes that improve bounds
Cuts are expensive to generate upfront
Example: Gomory cuts, problem-specific cuts
Custom Heuristics:
Have domain knowledge for generating good solutions
Want to guide search with problem-specific logic
Example: Construction heuristics
Example: Lazy Constraints¶
Note
Direct callback support is planned for future LumiX versions. Currently, you can access the underlying solver model for callback implementation.
from lumix import LXOptimizer
# Build model
model = build_tsp_model(cities)
# Create optimizer
optimizer = LXOptimizer().use_solver("gurobi")
# Access underlying Gurobi model
gurobi_model = optimizer._solver.build_model(model)
# Define lazy constraint callback (Gurobi-specific)
def subtour_callback(model, where):
if where == GRB.Callback.MIPSOL:
# Get current solution
vals = model.cbGetSolution(model._vars)
# Check for subtours
subtours = find_subtours(vals)
# Add lazy constraint if subtour found
for subtour in subtours:
model.cbLazy(sum(vars[i,j] for i,j in subtour) <= len(subtour) - 1)
gurobi_model.optimize(subtour_callback)
Supported Solvers¶
Solver |
Supported |
Notes |
|---|---|---|
OR-Tools |
✗ |
Not available |
Gurobi |
✓ |
Full callback support |
CPLEX |
✓ |
Full callback support |
GLPK |
✗ |
Not available |
CP-SAT |
✗ |
Not available |
Solution Pools (Gurobi/CPLEX Only)¶
Solution pools store multiple feasible solutions for MIP problems.
When to Use¶
Good for:
Need multiple diverse solutions (backup plans)
Want to present alternatives to decision makers
Post-processing to select “best” solution based on additional criteria
Example: Finding Multiple Solutions¶
from lumix import LXOptimizer
# Access underlying Gurobi model
optimizer = LXOptimizer().use_solver("gurobi")
gurobi_model = optimizer._solver.build_model(model)
# Request multiple solutions
gurobi_model.setParam('PoolSolutions', 10) # Keep up to 10 solutions
gurobi_model.setParam('PoolSearchMode', 2) # Find diverse solutions
gurobi_model.optimize()
# Access solution pool
num_solutions = gurobi_model.SolCount
print(f"Found {num_solutions} solutions")
for i in range(min(5, num_solutions)):
gurobi_model.setParam('SolutionNumber', i)
obj_val = gurobi_model.PoolObjVal
print(f"Solution {i+1}: objective = {obj_val}")
Supported Solvers¶
Solver |
Supported |
Notes |
|---|---|---|
OR-Tools |
✗ |
Not available |
Gurobi |
✓ |
Full solution pool support |
CPLEX |
✓ |
Solution pool support |
GLPK |
✗ |
Not available |
CP-SAT |
✗ |
Not available |
IIS and Conflict Refinement¶
IIS (Irreducible Inconsistent Subsystem) and conflict refinement help debug infeasible models.
What is IIS?¶
An IIS is a minimal subset of constraints that make the model infeasible:
Removing any single constraint from the IIS makes it feasible
Helps identify which constraints conflict
Example: Computing IIS¶
from lumix import LXOptimizer
# Solve model
optimizer = LXOptimizer().use_solver("gurobi")
solution = optimizer.solve(model)
if solution.status == "infeasible":
# Access underlying Gurobi model
gurobi_model = optimizer._solver.get_solver_model()
# Compute IIS
gurobi_model.computeIIS()
gurobi_model.write("model.ilp") # Write IIS to file
# Print conflicting constraints
print("Conflicting constraints:")
for constr in gurobi_model.getConstrs():
if constr.IISConstr:
print(f" {constr.ConstrName}")
Debugging Infeasible Models¶
Workflow:
Solve and check status
solution = optimizer.solve(model) if solution.status == "infeasible": print("Model is infeasible!")
Compute IIS
gurobi_model.computeIIS()
Identify conflicts
Check which constraints are in the IIS
Fix model
Options:
Remove or relax conflicting constraints
Adjust constraint bounds
Add slack variables with penalties
Example: Relaxing Constraints¶
# Add slack variables to constraints
for constraint in conflicting_constraints:
slack = LXVariable(f"slack_{constraint.name}").continuous().bounds(lower=0)
model.add_variable(slack)
# Add slack to constraint
constraint.expression.add_term(slack, 1.0)
# Add penalty for slack in objective
for slack_var in slack_variables:
objective.add_term(slack_var, -1000) # Large penalty
# Re-solve
solution = optimizer.solve(model)
Supported Solvers¶
Solver |
Supported |
Notes |
|---|---|---|
OR-Tools |
✗ |
Not available |
Gurobi |
✓ |
computeIIS() |
CPLEX |
✓ |
Conflict refinement |
GLPK |
✗ |
Not available |
CP-SAT |
✗ |
Not available |
Best Practices¶
Warm Start¶
Incremental Solving
Solve a sequence of similar models to benefit from warm start
Small Changes Only
Works best when models differ only in parameter values, not structure
Monitor Performance
Compare solve times with and without warm start
Sensitivity Analysis¶
LP Only (for meaningful results)
Shadow prices are most reliable for pure LP or after fixing integer variables
Valid Range
Shadow prices only valid for small changes in RHS (within basis stability range)
Use for Decisions
Identify which resources to expand, which constraints to relax
Callbacks¶
Profile First
Only use if default solver performance is insufficient
Keep Simple
Complex callbacks can slow solving more than they help
Test Thoroughly
Incorrect callbacks can produce wrong results
IIS/Conflict Refinement¶
Simplify First
Try to identify obvious conflicts before computing IIS
Iterative Debugging
Fix one conflict at a time, re-solve, repeat
Consider Relaxation
Add slack variables with penalties rather than removing constraints
Feature Availability Summary¶
Feature |
OR-Tools |
Gurobi |
CPLEX |
GLPK |
CP-SAT |
|---|---|---|---|---|---|
Warm Start |
✓ |
✓ |
✓ |
✗ |
✓ |
Sensitivity Analysis |
✗ |
✓ |
✓ |
✓ (LP) |
✗ |
Callbacks |
✗ |
✓ |
✓ |
✗ |
✗ |
Solution Pools |
✗ |
✓ |
✓ |
✗ |
✗ |
IIS/Conflict |
✗ |
✓ |
✓ |
✗ |
✗ |
Next Steps¶
Solver Capabilities - Full capability matrix
Choosing a Solver - Choose solver based on features needed
Using the Optimizer - Using the optimizer API
Extending Solvers - Implementing custom solvers
Gurobi Callback Reference: https://www.gurobi.com/documentation/
CPLEX Callback Reference: https://www.ibm.com/docs/en/icos/