Weighted Goal Programming¶
Learn how to use weighted goal programming to solve multi-objective problems with a single optimization run.
Overview¶
Weighted goal programming combines all goals into a single objective function using weighted sums of deviations. This approach:
Solves the problem in one optimization run
Uses exponential weight scaling to respect priority hierarchy
Is computationally efficient (single solve)
Provides a Pareto-optimal solution
When to Use¶
Weighted goal programming is ideal when:
You want a single optimal solution
You have clear priority hierarchy but can accept some flexibility
Computational efficiency is important
You’re comfortable with weighted trade-offs within priority levels
Not recommended when:
Priorities must be absolutely enforced (use Sequential Goal Programming instead)
You need exact lexicographic optimization
How It Works¶
Weight Scaling¶
LumiX automatically scales weights exponentially based on priority:
Priority 1 → Weight = 10^6
Priority 2 → Weight = 10^5
Priority 3 → Weight = 10^4
...
This ensures higher priorities effectively dominate lower priorities in the objective.
Mathematical formulation:
Where:
\(P\) = number of priorities
\(G_p\) = set of goals at priority \(p\)
\(w_g\) = weight for goal \(g\)
\(d_g^+, d_g^-\) = positive/negative deviations
Objective Construction¶
For each goal, the system:
Determines which deviation(s) to minimize based on constraint sense
Applies the goal’s weight
Scales by priority level weight
Sums all weighted deviations into single objective
LE (≤): minimize positive deviation (over-achievement)
overtime <= 40 → minimize pos_dev
GE (≥): minimize negative deviation (under-achievement)
demand >= 1000 → minimize neg_dev
EQ (=): minimize both deviations
budget == 50000 → minimize (pos_dev + neg_dev)
Basic Usage¶
Single-Priority Goals¶
from lumix import LXModel, LXVariable, LXConstraint, LXOptimizer
from lumix.core.expressions import LXLinearExpression
# Define variables
production = (
LXVariable[Product, float]("production")
.continuous()
.bounds(lower=0)
.indexed_by(lambda p: p.id)
.from_data(products)
)
# Goal: Meet demand (all same priority)
demand_goal = (
LXConstraint[Product]("demand_goal")
.expression(
LXLinearExpression()
.add_term(production, coeff=1.0)
)
.ge()
.rhs(lambda p: p.demand_target)
.as_goal(priority=1, weight=1.0)
.from_data(products)
)
# Build and solve
model = (
LXModel("production")
.add_variable(production)
.add_constraint(demand_goal)
)
optimizer = LXOptimizer().use_solver("gurobi")
solution = optimizer.solve(model)
# Analyze results
for product_id, qty in solution.get_mapped(production).items():
print(f"Product {product_id}: {qty} units")
# Check goal achievement
if solution.is_goal_satisfied("demand_goal"):
print("All demand targets met!")
else:
deviations = solution.get_goal_deviations("demand_goal")
total_under = sum(deviations['neg'].values())
print(f"Total under-production: {total_under:.2f}")
Multi-Priority Goals¶
# Priority 1: Meet demand (highest)
demand_goal = (
LXConstraint[Product]("demand")
.expression(production_expr)
.ge()
.rhs(lambda p: p.demand)
.as_goal(priority=1, weight=1.0)
.from_data(products)
)
# Priority 2: Maintain quality
quality_goal = (
LXConstraint("quality")
.expression(quality_expr)
.ge()
.rhs(0.95)
.as_goal(priority=2, weight=1.0)
)
# Priority 3: Control overtime
overtime_goal = (
LXConstraint("overtime")
.expression(hours_expr)
.le()
.rhs(40)
.as_goal(priority=3, weight=0.5) # Lower weight
)
model = (
LXModel("multi_priority")
.add_variable(production)
.add_constraint(demand_goal)
.add_constraint(quality_goal)
.add_constraint(overtime_goal)
)
solution = optimizer.solve(model)
# With exponential scaling:
# Priority 1: weight = 1.0 × 10^6 = 1,000,000
# Priority 2: weight = 1.0 × 10^5 = 100,000
# Priority 3: weight = 0.5 × 10^4 = 5,000
# Priority 1 goals dominate the objective
Using Weights¶
Within Same Priority¶
Weights control relative importance within the same priority level:
# Both priority 1, but different weights
critical_product = (
LXConstraint("product_a_demand")
.expression(production_a_expr)
.ge()
.rhs(100)
.as_goal(priority=1, weight=3.0) # 3× more important
)
normal_product = (
LXConstraint("product_b_demand")
.expression(production_b_expr)
.ge()
.rhs(100)
.as_goal(priority=1, weight=1.0)
)
# Effective weights:
# Product A: 3.0 × 10^6 = 3,000,000
# Product B: 1.0 × 10^6 = 1,000,000
# System will favor meeting Product A's goal
Asymmetric Deviation Costs¶
# Inventory goal where under-stock is worse than over-stock
inventory_goal = (
LXConstraint[Product]("inventory")
.expression(inventory_expr)
.eq() # Target exact amount
.rhs(lambda p: p.target_inventory)
.as_goal(priority=1, weight=1.0)
.from_data(products)
)
# Note: EQ goals minimize BOTH pos and neg deviations equally
# For asymmetric costs, use two separate constraints:
# Penalize under-stock more
min_inventory = (
LXConstraint[Product]("min_inventory")
.expression(inventory_expr)
.ge()
.rhs(lambda p: p.target_inventory)
.as_goal(priority=1, weight=3.0) # Higher penalty
.from_data(products)
)
# Penalize over-stock less
max_inventory = (
LXConstraint[Product]("max_inventory")
.expression(inventory_expr)
.le()
.rhs(lambda p: p.target_inventory)
.as_goal(priority=1, weight=1.0) # Lower penalty
.from_data(products)
)
Combining with Custom Objectives¶
Priority 0 for Custom Objectives¶
Use priority 0 to include a traditional objective alongside goals:
# Custom objective: Maximize profit (priority 0)
profit_objective = (
LXConstraint("profit")
.expression(
LXLinearExpression()
.add_term(production, lambda p: p.profit_margin)
)
.ge()
.rhs(0)
.as_goal(priority=0, weight=1.0) # Priority 0!
.from_data(products)
)
# Regular goals (priority 1+)
demand_goal = (
LXConstraint[Product]("demand")
.expression(production_expr)
.ge()
.rhs(lambda p: p.demand)
.as_goal(priority=1, weight=1.0)
.from_data(products)
)
quality_goal = (
LXConstraint("quality")
.expression(quality_expr)
.ge()
.rhs(0.95)
.as_goal(priority=2, weight=1.0)
)
# Result:
# Priority 1 (demand) will be optimized first
# Then priority 2 (quality)
# Finally, profit will be maximized without hurting higher priorities
Practical Example¶
Production Planning with Multiple Goals¶
from dataclasses import dataclass
from typing import List
@dataclass
class Product:
id: str
demand_target: float
profit_margin: float
production_cost: float
@dataclass
class Resource:
id: str
capacity: float
cost_per_unit: float
# Data
products = [
Product("A", demand_target=100, profit_margin=10, production_cost=5),
Product("B", demand_target=150, profit_margin=12, production_cost=6),
Product("C", demand_target=200, profit_margin=8, production_cost=4),
]
resources = [
Resource("labor", capacity=500, cost_per_unit=20),
Resource("material", capacity=1000, cost_per_unit=5),
]
# Variables
production = (
LXVariable[Product, float]("production")
.continuous()
.bounds(lower=0)
.indexed_by(lambda p: p.id)
.from_data(products)
)
resource_usage = (
LXVariable[Resource, float]("resource_usage")
.continuous()
.bounds(lower=0)
.indexed_by(lambda r: r.id)
.from_data(resources)
)
# Hard constraint: Resource capacity
capacity_constraint = (
LXConstraint[Resource]("capacity")
.expression(
LXLinearExpression()
.add_term(resource_usage, coeff=1.0)
)
.le()
.rhs(lambda r: r.capacity)
.from_data(resources)
)
# Priority 0: Maximize profit (custom objective)
profit_goal = (
LXConstraint("profit")
.expression(
LXLinearExpression()
.add_term(production, lambda p: p.profit_margin)
)
.ge()
.rhs(0)
.as_goal(priority=0, weight=1.0)
)
# Priority 1: Meet demand (highest priority goal)
demand_goal = (
LXConstraint[Product]("demand")
.expression(
LXLinearExpression()
.add_term(production, coeff=1.0)
)
.ge()
.rhs(lambda p: p.demand_target)
.as_goal(priority=1, weight=1.0)
.from_data(products)
)
# Priority 2: Efficient resource utilization (lower priority)
# Want to use exactly 80% of capacity
utilization_goal = (
LXConstraint[Resource]("utilization")
.expression(
LXLinearExpression()
.add_term(resource_usage, coeff=1.0)
)
.eq() # Exact target
.rhs(lambda r: r.capacity * 0.8)
.as_goal(priority=2, weight=1.0)
.from_data(resources)
)
# Build model
model = (
LXModel("production_planning")
.add_variable(production)
.add_variable(resource_usage)
.add_constraint(capacity_constraint) # Hard constraint
.add_constraint(profit_goal)
.add_constraint(demand_goal)
.add_constraint(utilization_goal)
)
# Solve
optimizer = LXOptimizer().use_solver("gurobi")
solution = optimizer.solve(model)
# Analyze results
print("=" * 80)
print("PRODUCTION PLANNING RESULTS")
print("=" * 80)
if solution.is_optimal():
print(f"\nObjective Value: {solution.objective_value:.2f}")
print(f"Solve Time: {solution.solve_time:.3f}s")
# Production quantities
print("\nProduction Plan:")
print("-" * 80)
for product_id, qty in solution.get_mapped(production).items():
product = next(p for p in products if p.id == product_id)
status = "✓" if qty >= product.demand_target else "✗"
print(f"{status} Product {product_id}: {qty:6.2f} units (target: {product.demand_target})")
# Resource usage
print("\nResource Utilization:")
print("-" * 80)
for resource_id, usage in solution.get_mapped(resource_usage).items():
resource = next(r for r in resources if r.id == resource_id)
pct = (usage / resource.capacity) * 100
print(f"{resource_id}: {usage:6.2f} / {resource.capacity} ({pct:.1f}%)")
# Goal achievement
print("\nGoal Achievement:")
print("-" * 80)
goals = [
("profit", "Maximize Profit", 0),
("demand", "Meet Demand", 1),
("utilization", "Target Utilization", 2),
]
for goal_name, description, priority in goals:
satisfied = solution.is_goal_satisfied(goal_name)
status = "✓ Achieved" if satisfied else "✗ Not Achieved"
print(f"\nPriority {priority}: {description}")
print(f" Status: {status}")
if not satisfied:
total_dev = solution.get_total_deviation(goal_name)
print(f" Total Deviation: {total_dev:.2f}")
deviations = solution.get_goal_deviations(goal_name)
if isinstance(deviations['pos'], dict):
total_pos = sum(deviations['pos'].values())
total_neg = sum(deviations['neg'].values())
else:
total_pos = deviations['pos']
total_neg = deviations['neg']
if total_pos > 1e-6:
print(f" Over-achievement: {total_pos:.2f}")
if total_neg > 1e-6:
print(f" Under-achievement: {total_neg:.2f}")
Advanced Techniques¶
Dynamic Weight Calculation¶
# Calculate weights based on goal importance
def calculate_goal_weight(product: Product) -> float:
"""Weight based on profit margin and criticality."""
base_weight = 1.0
profit_factor = product.profit_margin / 10.0 # Normalize
criticality = 2.0 if product.is_critical else 1.0
return base_weight * profit_factor * criticality
demand_goal = (
LXConstraint[Product]("demand")
.expression(production_expr)
.ge()
.rhs(lambda p: p.demand_target)
.as_goal(
priority=1,
weight=1.0 # Could use lambda here if API supported it
)
.from_data(products)
)
Conditional Goals¶
# Only create goals for active products
active_products = [p for p in products if p.is_active]
demand_goal = (
LXConstraint[Product]("demand")
.expression(production_expr)
.ge()
.rhs(lambda p: p.demand_target)
.where(lambda p: p.is_active) # Filter
.as_goal(priority=1, weight=1.0)
.from_data(active_products)
)
Best Practices¶
Use Clear Priority Hierarchy
# Good: Distinct priorities for different objectives safety_goal.as_goal(priority=1, weight=1.0) # Safety first! quality_goal.as_goal(priority=2, weight=1.0) # Then quality cost_goal.as_goal(priority=3, weight=1.0) # Then cost # Avoid: Too many priority levels # More than 4-5 priorities usually indicates poor problem design
Set Realistic Weights
# Good: Weights reflect relative importance within priority high_value_customer.as_goal(priority=1, weight=2.0) normal_customer.as_goal(priority=1, weight=1.0) # Avoid: Extreme weight differences within same priority # If weights differ by >1000×, consider separate priorities instead
Monitor Goal Achievement
# Always check which goals were achieved goals_to_check = ["demand", "quality", "overtime"] achieved = sum( 1 for g in goals_to_check if solution.is_goal_satisfied(g) ) print(f"Goals achieved: {achieved}/{len(goals_to_check)}") # Analyze unmet goals for goal in goals_to_check: if not solution.is_goal_satisfied(goal): dev = solution.get_total_deviation(goal) print(f"⚠ {goal}: deviation = {dev:.2f}")
Document Goal Rationale
# Good: Clear documentation of goal purpose and priorities demand_goal = ( LXConstraint[Product]("demand") .expression(production_expr) .ge() .rhs(lambda p: p.demand_target) # Priority 1: Customer satisfaction is top priority # Weight 1.0: All customers equally important .as_goal(priority=1, weight=1.0) .from_data(products) )
Troubleshooting¶
Goals Not Respected¶
Issue: Lower priority goals seem to affect higher priority goals.
Solution: Check if priority weights are too close. The default exponential scaling (10^6, 10^5, 10^4) should be sufficient, but you can verify:
from lumix.goal_programming import priority_to_weight
# Check weight scaling
for priority in [1, 2, 3]:
weight = priority_to_weight(priority)
print(f"Priority {priority}: {weight:.0f}")
# If needed, use sequential mode for strict enforcement
model.set_goal_mode("sequential")
Infeasible Solution¶
Issue: Model returns infeasible even with goal programming.
Cause: Hard constraints (non-goal constraints) are conflicting.
Solution: Convert more constraints to goals:
# Before: Hard constraint might cause infeasibility
overtime_constraint = (
LXConstraint("overtime")
.expression(hours_expr)
.le()
.rhs(40)
# No .as_goal() - hard constraint
)
# After: Soft constraint allows violation if needed
overtime_goal = (
LXConstraint("overtime")
.expression(hours_expr)
.le()
.rhs(40)
.as_goal(priority=2, weight=1.0) # Now soft
)
Unexpected Deviations¶
Issue: Goals have unexpected deviation values.
Debugging:
# Inspect all goal deviations
for goal_name in ["demand", "quality", "overtime"]:
deviations = solution.get_goal_deviations(goal_name)
print(f"\n{goal_name}:")
print(f" Positive deviation: {deviations['pos']}")
print(f" Negative deviation: {deviations['neg']}")
# For indexed goals, show breakdown
if isinstance(deviations['pos'], dict):
for key in deviations['pos'].keys():
pos = deviations['pos'][key]
neg = deviations['neg'][key]
if pos > 1e-6 or neg > 1e-6:
print(f" {key}: pos={pos:.2f}, neg={neg:.2f}")
Next Steps¶
Sequential Goal Programming - Learn about lexicographic goal programming
Constraint Relaxation - Understand constraint relaxation mechanics
Objective Building - Advanced objective construction
Goal Programming Module API - Full API reference
Goal Programming Solutions - Accessing goal programming solutions