Accessing Solutions¶
Learn how to access variable values and solution metadata from optimization solutions.
Solution Object¶
The LXSolution class provides a comprehensive container for
optimization results, including variable values, metadata, and optional sensitivity information.
Basic Structure¶
@dataclass
class LXSolution:
objective_value: float
status: str
solve_time: float
variables: Dict[str, Union[float, Dict[Any, float]]]
mapped: Dict[str, Dict[Any, float]]
shadow_prices: Dict[str, float] = field(default_factory=dict)
reduced_costs: Dict[str, float] = field(default_factory=dict)
gap: Optional[float] = None
iterations: Optional[int] = None
nodes: Optional[int] = None
Accessing Variable Values¶
By Variable Name¶
Access values using the variable name (string):
# Scalar variable
budget_used = solution.variables["budget"]
print(f"Budget used: ${budget_used:.2f}")
# Indexed variable (dict of values)
production_values = solution.variables["production"]
# {0: 10.0, 1: 20.0, 2: 15.0}
When to use: Quick access when you know the variable name.
By LXVariable Object¶
Type-safe access using the variable definition:
production = LXVariable[Product, float]("production")
# ... model building ...
solution = optimizer.solve(model)
# Type-safe access
value = solution.get_variable(production)
When to use: Preferred for type safety and IDE autocomplete.
Working with Different Variable Types¶
Scalar Variables¶
Variables with a single value:
# Define scalar variable
total_cost = LXVariable[None, float]("total_cost").continuous()
# Access value
cost = solution.get_variable(total_cost)
print(f"Total cost: ${cost:.2f}")
Single-Indexed Variables¶
Variables indexed by one dimension:
from dataclasses import dataclass
@dataclass
class Product:
id: str
name: str
# Define indexed variable
production = (
LXVariable[Product, float]("production")
.continuous()
.indexed_by(lambda p: p.id)
.from_data(products)
)
# Access all values (solver indices)
prod_values = solution.get_variable(production)
# Result: {0: 10.0, 1: 20.0, 2: 15.0}
# Access mapped values (original keys)
mapped_values = solution.get_mapped(production)
# Result: {"product_A": 10.0, "product_B": 20.0, "product_C": 15.0}
# Iterate over mapped values
for product_id, quantity in mapped_values.items():
print(f"Product {product_id}: {quantity} units")
Multi-Indexed Variables¶
Variables indexed by multiple dimensions:
from typing import Tuple
@dataclass
class Driver:
id: int
name: str
@dataclass
class Date:
date: str
# Define multi-indexed variable
assignment = (
LXVariable[Tuple[Driver, Date], int]("assignment")
.binary()
.indexed_by_product(
LXIndexDimension(Driver, lambda d: d.id).from_data(drivers),
LXIndexDimension(Date, lambda dt: dt.date).from_data(dates),
)
)
# Access values
assignments = solution.get_mapped(assignment)
# Result: {(1, "2024-01-01"): 1, (1, "2024-01-02"): 0, ...}
# Process multi-indexed results
for (driver_id, date_str), value in assignments.items():
if value > 0.5: # For binary variables
print(f"Driver {driver_id} assigned on {date_str}")
Mapped vs Direct Access¶
Understanding the Difference¶
LumiX provides two ways to access variable values:
Direct access (
solution.variables): Uses solver’s internal indicesMapped access (
solution.mapped): Uses your data’s original keys
# Direct access - solver indices
solution.variables["production"]
# {0: 10.0, 1: 20.0, 2: 15.0}
# Mapped access - original keys
solution.mapped["production"]
# {"product_A": 10.0, "product_B": 20.0, "product_C": 15.0}
When to Use Each¶
Use mapped access (get_mapped()) when:
Working with your original data models
Need to correlate solutions with business objects
Building reports or outputs
Integrating with databases/ORMs
Use direct access (variables) when:
Debugging solver behavior
Validating solution structure
Working with solver-specific tools
Example Comparison¶
# Mapped access (preferred for most use cases)
for product_id, qty in solution.get_mapped(production).items():
product = products_by_id[product_id]
print(f"{product.name}: {qty} units @ ${product.price}")
# Direct access (requires manual index mapping)
for idx, qty in solution.variables["production"].items():
product = products[idx] # Assumes index order is preserved
print(f"{product.name}: {qty}")
Solution Metadata¶
Status Checking¶
Check the optimization status:
# Check if optimal
if solution.is_optimal():
print("Found optimal solution")
# Check if feasible (optimal or sub-optimal)
if solution.is_feasible():
print("Found feasible solution")
# Raw status string
print(f"Status: {solution.status}")
# Common values: "optimal", "feasible", "infeasible", "unbounded"
Objective Value¶
print(f"Objective value: {solution.objective_value:.6f}")
# For minimization
print(f"Minimum cost: ${solution.objective_value:,.2f}")
# For maximization
print(f"Maximum profit: ${solution.objective_value:,.2f}")
Solve Time¶
print(f"Solved in {solution.solve_time:.3f} seconds")
# For performance tracking
if solution.solve_time > 60:
print(f"Warning: Long solve time ({solution.solve_time:.1f}s)")
Solver-Specific Information¶
Some solvers provide additional information:
# MIP gap (for integer programs)
if solution.gap is not None:
print(f"Optimality gap: {solution.gap * 100:.2f}%")
# Iteration count
if solution.iterations is not None:
print(f"Iterations: {solution.iterations}")
# Node count (for branch-and-bound)
if solution.nodes is not None:
print(f"Nodes explored: {solution.nodes}")
Solution Summary¶
Get a formatted summary:
print(solution.summary())
Output:
Status: optimal
Objective: 12345.678900
Solve time: 0.123s
Non-zero variables: 42/100
Gap: 0.00%
Iterations: 125
Nodes: 0
Filtering and Processing Results¶
Filter Near-Zero Values¶
# Filter out numerical noise
epsilon = 1e-6
for key, value in solution.get_mapped(production).items():
if abs(value) > epsilon:
print(f"{key}: {value}")
Filter Binary Variables¶
# For binary/integer variables
for (worker_id, task_id), assigned in solution.get_mapped(assignment).items():
if assigned > 0.5: # Threshold for binary variables
print(f"Worker {worker_id} assigned to task {task_id}")
Sort Results¶
# Sort by value (descending)
production_values = solution.get_mapped(production)
sorted_products = sorted(
production_values.items(),
key=lambda x: x[1],
reverse=True
)
print("Top 5 products by production:")
for product_id, quantity in sorted_products[:5]:
print(f" {product_id}: {quantity}")
Aggregate Results¶
# Total production
total = sum(solution.get_mapped(production).values())
print(f"Total production: {total}")
# Production by category
from collections import defaultdict
by_category = defaultdict(float)
for product_id, quantity in solution.get_mapped(production).items():
category = products_by_id[product_id].category
by_category[category] += quantity
for category, total in by_category.items():
print(f"{category}: {total}")
Error Handling¶
Handle Missing Values¶
# Check if variable exists
if "production" in solution.variables:
values = solution.variables["production"]
else:
print("Variable 'production' not found in solution")
# Use get() with default
budget = solution.variables.get("budget", 0.0)
Handle Infeasible Solutions¶
solution = optimizer.solve(model)
if not solution.is_feasible():
print(f"Model is infeasible: {solution.status}")
print("Possible issues:")
print(" - Conflicting constraints")
print(" - Over-constrained model")
print(" - Incorrect bounds")
return None
# Continue with feasible solution
return solution.get_mapped(production)
Handle Unbounded Solutions¶
if solution.status.lower() == "unbounded":
print("Model is unbounded")
print("Possible issues:")
print(" - Missing constraints")
print(" - Incorrect objective direction")
print(" - Missing variable bounds")
Best Practices¶
Always Check Status First
solution = optimizer.solve(model) if not solution.is_optimal(): print(f"Warning: Solution status is {solution.status}") if not solution.is_feasible(): return None # Don't process infeasible solutions
Use Mapped Access for Business Logic
# Good: Works with your data model for product_id, qty in solution.get_mapped(production).items(): product = get_product(product_id) save_production_plan(product, qty) # Less ideal: Requires index management for idx, qty in solution.variables["production"].items(): product = products[idx] # Fragile
Handle Optional Metadata Gracefully
# Solver may not provide all metadata if solution.gap is not None: print(f"Gap: {solution.gap * 100:.2f}%") else: print("Gap information not available")
Filter Numerical Noise
epsilon = 1e-6 significant_values = { k: v for k, v in solution.get_mapped(production).items() if abs(v) > epsilon }
Common Patterns¶
Production Planning¶
def analyze_production_plan(solution, products):
"""Analyze and report production plan."""
if not solution.is_optimal():
print(f"Warning: Solution status is {solution.status}")
production_values = solution.get_mapped(production)
# Calculate metrics
total_units = sum(production_values.values())
total_value = sum(
qty * products_by_id[pid].price
for pid, qty in production_values.items()
)
# Generate report
print(f"Total production: {total_units:,.0f} units")
print(f"Total value: ${total_value:,.2f}")
print(f"Profit: ${solution.objective_value:,.2f}")
# List high-volume products
high_volume = [
(pid, qty) for pid, qty in production_values.items()
if qty > 1000
]
print(f"\nHigh-volume products ({len(high_volume)}):")
for product_id, quantity in sorted(high_volume, key=lambda x: -x[1]):
product = products_by_id[product_id]
print(f" {product.name}: {quantity:,.0f} units")
Resource Allocation¶
def analyze_resource_allocation(solution, resources):
"""Analyze resource allocation and utilization."""
allocation = solution.get_mapped(resource_allocation)
# Calculate utilization by resource
utilization = {}
for (resource_id, task_id), amount in allocation.items():
if resource_id not in utilization:
utilization[resource_id] = 0
utilization[resource_id] += amount
# Report utilization
for resource_id, used in utilization.items():
resource = resources_by_id[resource_id]
pct = (used / resource.capacity) * 100
print(f"{resource.name}: {pct:.1f}% utilized ({used}/{resource.capacity})")
Next Steps¶
Sensitivity Analysis - Analyze shadow prices and reduced costs
Goal Programming Solutions - Work with goal programming solutions
Solution Mapping - Map solutions to ORM models
Solution Module API - Full API reference