Scenario Analysis¶
Scenario analysis enables systematic comparison of multiple what-if scenarios to understand how different business conditions or assumptions affect optimal decisions.
Overview¶
Scenario analysis answers questions like:
How do optimistic vs. pessimistic forecasts affect our plan?
What if we have 20% more capacity?
How would a budget cut impact operations?
Which strategic option performs best?
The LXScenarioAnalyzer allows you to:
Define multiple scenarios with different parameter combinations
Run all scenarios in parallel
Compare results side-by-side
Identify the best scenario for your objectives
Key Concepts¶
Scenarios¶
A scenario represents a set of modifications to your base model that reflect a particular business condition or assumption.
Components:
Name: Unique identifier for the scenario
Description: Human-readable explanation
Modifications: List of parameter changes to apply
Example Scenarios:
“High Demand”: 30% increase in demand constraints
“Cost Reduction”: 15% reduction in operating costs
“Capacity Expansion”: Additional warehouse space available
“Supply Chain Disruption”: Reduced supplier availability
Modifications¶
A modification specifies how to change a model parameter.
Types:
Constraint RHS: Set, add, or multiply right-hand side values
Variable Bounds: Modify lower or upper bounds
Objective Coefficients: Change objective function coefficients (future)
Operations:
set_value: Replace current valueadd: Add to current valuemultiply: Multiply current value by factor
Scenario Comparison¶
After running scenarios, you can:
Compare objective values across scenarios
Calculate percentage differences from baseline
Identify best and worst cases
Generate comparison reports
Basic Usage¶
Creating Scenarios¶
from lumix.analysis import LXScenario
# Define a scenario
high_capacity = (
LXScenario("high_capacity")
.modify_constraint_rhs("capacity", multiply=1.5)
.describe("50% capacity increase")
)
# Multiple modifications
optimistic = (
LXScenario("optimistic")
.modify_constraint_rhs("capacity", multiply=1.3)
.modify_constraint_rhs("budget", add=50000)
.modify_variable_bound("production", lower=100)
.describe("Optimistic market conditions")
)
Creating the Analyzer¶
from lumix.analysis import LXScenarioAnalyzer
# Create analyzer with baseline
analyzer = LXScenarioAnalyzer(
base_model=model,
optimizer=optimizer,
include_baseline=True # Include unmodified model as "baseline"
)
Adding Scenarios¶
# Add single scenario
analyzer.add_scenario(high_capacity)
# Add multiple scenarios
analyzer.add_scenarios(optimistic, pessimistic, conservative)
# Or use fluent API
analyzer = (
LXScenarioAnalyzer(model, optimizer)
.add_scenario(high_capacity)
.add_scenario(low_capacity)
.add_scenario(normal_capacity)
)
Running Scenarios¶
# Run all scenarios
results = analyzer.run_all_scenarios()
# Access individual results
high_cap_solution = results["high_capacity"]
print(f"High capacity objective: ${high_cap_solution.objective_value:,.2f}")
# Run single scenario
solution = analyzer.run_scenario("optimistic")
Comparing Results¶
# Generate comparison report
report = analyzer.compare_scenarios()
print(report)
# Example output:
# Scenario Comparison
# ================================================================================
# Baseline Objective: 125,000.00
#
# Scenario Objective Status vs Baseline
# --------------------------------------------------------------------------------
# high_capacity 156,250.00 OPTIMAL +25.00%
# optimistic 145,000.00 OPTIMAL +16.00%
# baseline 125,000.00 OPTIMAL -
# conservative 110,000.00 OPTIMAL -12.00%
# pessimistic 95,000.00 OPTIMAL -24.00%
Finding Best Scenario¶
# For maximization
best = analyzer.get_best_scenario(maximize=True)
print(f"Best scenario: {best}")
# For minimization
best = analyzer.get_best_scenario(maximize=False)
Modification Types¶
Constraint RHS Modifications¶
scenario = LXScenario("capacity_changes")
# Set to specific value
scenario.modify_constraint_rhs("max_capacity", set_value=1500.0)
# Add to current value
scenario.modify_constraint_rhs("max_capacity", add=200.0)
# Multiply by factor
scenario.modify_constraint_rhs("max_capacity", multiply=1.25)
Variable Bound Modifications¶
scenario = LXScenario("bound_changes")
# Set lower bound
scenario.modify_variable_bound("production", lower=100.0)
# Set upper bound
scenario.modify_variable_bound("production", upper=500.0)
# Set both
scenario.modify_variable_bound("inventory", lower=50.0, upper=200.0)
Custom Modifications¶
from lumix.analysis import LXScenarioModification
# Create custom modification
mod = LXScenarioModification(
target_type="constraint",
target_name="demand",
modification_type="rhs_multiply",
value=1.2,
description="20% demand increase"
)
scenario = LXScenario("custom").add_custom_modification(mod)
Practical Examples¶
Example 1: Demand Scenarios¶
from lumix.analysis import LXScenario, LXScenarioAnalyzer
# Create demand scenarios
high_demand = (
LXScenario("high_demand")
.modify_constraint_rhs("demand", multiply=1.3)
.describe("30% increase in demand")
)
normal_demand = (
LXScenario("normal_demand")
.modify_constraint_rhs("demand", multiply=1.0)
.describe("Expected demand")
)
low_demand = (
LXScenario("low_demand")
.modify_constraint_rhs("demand", multiply=0.7)
.describe("30% decrease in demand")
)
# Run analysis
analyzer = (
LXScenarioAnalyzer(model, optimizer)
.add_scenarios(high_demand, normal_demand, low_demand)
)
results = analyzer.run_all_scenarios()
# Compare
print(analyzer.compare_scenarios())
# Decision making
best = analyzer.get_best_scenario()
worst = analyzer.get_best_scenario(maximize=False)
print(f"\nBest case ({best}):")
print(f" Objective: ${results[best].objective_value:,.2f}")
print(f"\nWorst case ({worst}):")
print(f" Objective: ${results[worst].objective_value:,.2f}")
# Range analysis
obj_range = (
results[best].objective_value - results[worst].objective_value
)
print(f"\nObjective range: ${obj_range:,.2f}")
Example 2: Investment Options¶
# Compare different investment strategies
warehouse_expansion = (
LXScenario("warehouse_expansion")
.modify_constraint_rhs("storage_capacity", multiply=2.0)
.modify_constraint_rhs("budget", add=-100000) # Cost
.describe("Double warehouse capacity ($100k)")
)
fleet_expansion = (
LXScenario("fleet_expansion")
.modify_constraint_rhs("truck_capacity", multiply=1.5)
.modify_constraint_rhs("budget", add=-75000) # Cost
.describe("50% more trucks ($75k)")
)
automation = (
LXScenario("automation")
.modify_constraint_rhs("labor_hours", multiply=0.6) # 40% reduction
.modify_constraint_rhs("budget", add=-150000) # Cost
.describe("Automation investment ($150k)")
)
# Run and compare
analyzer = LXScenarioAnalyzer(model, optimizer)
analyzer.add_scenarios(
warehouse_expansion,
fleet_expansion,
automation
)
results = analyzer.run_all_scenarios(include_baseline=True)
# Calculate ROI for each option
baseline_obj = results["baseline"].objective_value
print("Investment ROI Analysis:")
print("-" * 60)
investments = [
("warehouse_expansion", 100000),
("fleet_expansion", 75000),
("automation", 150000),
]
for name, cost in investments:
improvement = results[name].objective_value - baseline_obj
roi = (improvement / cost) * 100 if cost > 0 else 0
print(f"{name:25s}: ${improvement:10,.2f} ({roi:5.1f}% ROI)")
Example 3: Sensitivity to Multiple Parameters¶
# Test sensitivity to capacity at different levels
capacity_multipliers = [0.5, 0.7, 0.9, 1.0, 1.1, 1.3, 1.5, 2.0]
results = analyzer.sensitivity_to_parameter(
parameter_name="capacity",
values=capacity_multipliers,
modification_type="rhs_multiply",
target_type="constraint"
)
# Plot results
print("Capacity Sensitivity:")
print("-" * 60)
print(f"{'Multiplier':<12} {'Objective':>15} {'Change':>15}")
print("-" * 60)
baseline = results[1.0].objective_value
for multiplier in sorted(results.keys()):
obj = results[multiplier].objective_value
change = obj - baseline
print(f"{multiplier:<12.1f} ${obj:>14,.2f} ${change:>14,.2f}")
Example 4: Stress Testing¶
# Create worst-case scenario
worst_case = (
LXScenario("worst_case")
.modify_constraint_rhs("capacity", multiply=0.7) # 30% reduction
.modify_constraint_rhs("budget", multiply=0.8) # 20% cut
.modify_constraint_rhs("demand", multiply=1.3) # 30% increase
.describe("Worst case: reduced capacity, tight budget, high demand")
)
# Create best-case scenario
best_case = (
LXScenario("best_case")
.modify_constraint_rhs("capacity", multiply=1.3) # 30% increase
.modify_constraint_rhs("budget", multiply=1.2) # 20% more
.modify_constraint_rhs("demand", multiply=0.9) # 10% decrease
.describe("Best case: excess capacity, flexible budget, low demand")
)
# Run stress test
analyzer = (
LXScenarioAnalyzer(model, optimizer)
.add_scenarios(worst_case, best_case)
)
results = analyzer.run_all_scenarios(include_baseline=True)
# Analyze robustness
baseline_obj = results["baseline"].objective_value
worst_obj = results["worst_case"].objective_value
best_obj = results["best_case"].objective_value
downside_risk = (baseline_obj - worst_obj) / baseline_obj * 100
upside_potential = (best_obj - baseline_obj) / baseline_obj * 100
print("Stress Test Results:")
print(f" Baseline: ${baseline_obj:,.2f}")
print(f" Worst Case: ${worst_obj:,.2f} ({-downside_risk:.1f}%)")
print(f" Best Case: ${best_obj:,.2f} (+{upside_potential:.1f}%)")
print(f" Downside Risk: {downside_risk:.1f}%")
print(f" Upside Potential: {upside_potential:.1f}%")
Advanced Features¶
Conditional Modifications¶
# Use lambdas for dynamic modifications
def get_seasonal_multiplier(season):
return {"winter": 0.7, "spring": 1.0, "summer": 1.3, "fall": 0.9}[season]
for season in ["winter", "spring", "summer", "fall"]:
scenario = (
LXScenario(f"{season}_demand")
.modify_constraint_rhs(
"demand",
multiply=get_seasonal_multiplier(season)
)
.describe(f"{season.capitalize()} seasonal pattern")
)
analyzer.add_scenario(scenario)
Scenario Filtering¶
# Compare only specific scenarios
report = analyzer.compare_scenarios(
scenario_names=["high_capacity", "low_capacity"],
include_baseline=True,
sort_by_objective=True
)
Programmatic Scenario Generation¶
# Generate scenarios programmatically
for pct in range(-30, 31, 10): # -30% to +30% in 10% steps
multiplier = 1.0 + (pct / 100.0)
scenario = (
LXScenario(f"capacity_{pct:+d}pct")
.modify_constraint_rhs("capacity", multiply=multiplier)
.describe(f"Capacity {pct:+d}%")
)
analyzer.add_scenario(scenario)
# Run all programmatically generated scenarios
results = analyzer.run_all_scenarios()
Best Practices¶
Always Include Baseline
Compare scenarios against the unmodified model.
analyzer = LXScenarioAnalyzer(model, optimizer, include_baseline=True)
Use Descriptive Names and Descriptions
Make reports readable and understandable.
scenario = ( LXScenario("q4_holiday_surge") # Clear name .modify_constraint_rhs("demand", multiply=1.5) .describe("Q4 holiday season: 50% demand increase") # Explanation )
Test Extreme Cases
Include both optimistic and pessimistic scenarios.
# Not just likely scenarios scenarios = [ create_scenario("best_case", 1.5), create_scenario("likely", 1.1), create_scenario("worst_case", 0.6), ]
Validate Scenario Feasibility
Check that scenarios produce feasible solutions.
results = analyzer.run_all_scenarios() for name, solution in results.items(): if not solution.is_optimal(): print(f"Warning: {name} is infeasible or non-optimal")
Combine with Sensitivity Analysis
Use sensitivity to guide scenario design.
from lumix.analysis import LXSensitivityAnalyzer # Identify sensitive parameters sens = LXSensitivityAnalyzer(model, baseline_solution) bottlenecks = sens.identify_bottlenecks() # Create scenarios around bottlenecks for constraint in bottlenecks: scenario = LXScenario(f"{constraint}_relaxed") scenario.modify_constraint_rhs(constraint, multiply=1.2) analyzer.add_scenario(scenario)
Performance Considerations¶
Scenario analysis solves the model multiple times, which can be time-consuming for large models.
Optimization Tips:
Limit the number of scenarios for large models
Use warm starts if your solver supports it
Run scenarios in parallel (future feature)
Cache baseline solution to avoid re-solving
# Efficient scenario analysis
# 1. Solve baseline once
baseline = optimizer.solve(model)
# 2. Create analyzer with cached baseline
analyzer = LXScenarioAnalyzer(model, optimizer, include_baseline=False)
# 3. Use baseline for comparisons
# (Store baseline separately)
Next Steps¶
Sensitivity Analysis - Understand shadow prices and reduced costs
What-If Analysis - Interactive exploration of changes
Analysis Module API - Complete API reference
Analysis Architecture - Architecture details