Goal Programming Solutions¶
Learn how to work with goal programming solutions and analyze goal deviations.
Overview¶
Goal programming extends traditional optimization by allowing you to define multiple objectives as goals with target values. Instead of strict constraints, goals can be violated with penalties.
The LXSolution class provides methods to:
Access deviation values (positive and negative)
Check if goals are satisfied
Calculate total deviations
Analyze goal achievement
Goal Programming Concepts¶
What Are Goals?¶
A goal is a soft constraint that you’d like to achieve but can deviate from:
# Traditional hard constraint
production >= demand # Must be satisfied
# Goal constraint
production ≈ demand_target # Try to achieve, but can deviate
# Creates two deviation variables:
# - positive deviation (over-achievement)
# - negative deviation (under-achievement)
Deviation Variables¶
Each goal creates two deviation variables:
Positive deviation (
pos): Amount by which goal is exceededNegative deviation (neg``): Amount by which goal is under-achieved
# Example: demand_target = 1000
# If production = 1100:
# pos = 100 (over-production)
# neg = 0 (no under-production)
# If production = 900:
# pos = 0 (no over-production)
# neg = 100 (under-production)
Accessing Goal Deviations¶
Get Deviations¶
# Get deviations for a goal
deviations = solution.get_goal_deviations("production_target")
if deviations:
pos_dev = deviations["pos"] # Over-achievement
neg_dev = deviations["neg"] # Under-achievement
print(f"Positive deviation: {pos_dev}")
print(f"Negative deviation: {neg_dev}")
else:
print("Goal 'production_target' not found")
Return Value¶
The get_goal_deviations() method returns a dictionary:
{
"pos": Union[float, Dict[Any, float]],
"neg": Union[float, Dict[Any, float]]
}
For scalar goals:
deviations = solution.get_goal_deviations("total_cost_target")
# {'pos': 0.0, 'neg': 150.5}
For indexed goals:
deviations = solution.get_goal_deviations("demand_target")
# {
# 'pos': {'product_A': 10.0, 'product_B': 0.0},
# 'neg': {'product_A': 0.0, 'product_B': 5.0}
# }
Checking Goal Satisfaction¶
is_goal_satisfied()¶
Check if a goal is achieved within tolerance:
# Check with default tolerance (1e-6)
if solution.is_goal_satisfied("production_target"):
print("Production target achieved!")
# Check with custom tolerance
if solution.is_goal_satisfied("quality_target", tolerance=0.01):
print("Quality target achieved (within 1% tolerance)")
Return Values¶
True: Goal is satisfied (both deviations within tolerance)False: Goal is not satisfiedNone: Goal not found in solution
satisfied = solution.is_goal_satisfied("demand_target")
if satisfied is True:
print("Goal achieved")
elif satisfied is False:
print("Goal not achieved")
else:
print("Goal not found in solution")
Total Deviation¶
Get the sum of absolute deviations:
total_dev = solution.get_total_deviation("production_target")
if total_dev is not None:
print(f"Total deviation: {total_dev:.2f}")
else:
print("Goal not found")
Calculation:
# For scalar goals
total_deviation = abs(pos_dev) + abs(neg_dev)
# For indexed goals
total_deviation = sum(abs(v) for v in pos_dev.values()) + \\
sum(abs(v) for v in neg_dev.values())
Working with Goal Solutions¶
Analyzing Goal Achievement¶
def analyze_goal_achievement(solution, goal_names):
"""Analyze which goals are achieved."""
print("Goal Achievement Analysis")
print(f"{'Goal':<30} {'Satisfied':<12} {'Total Deviation'}")
print("-" * 70)
satisfied_count = 0
for goal_name in goal_names:
satisfied = solution.is_goal_satisfied(goal_name)
total_dev = solution.get_total_deviation(goal_name)
if satisfied:
satisfied_count += 1
status = "✓ Yes"
else:
status = "✗ No"
dev_str = f"{total_dev:.2f}" if total_dev else "N/A"
print(f"{goal_name:<30} {status:<12} {dev_str}")
print(f"\nGoals achieved: {satisfied_count}/{len(goal_names)}")
Prioritizing Unmet Goals¶
def prioritize_unmet_goals(solution, goals):
"""Rank goals by deviation for follow-up action."""
unmet_goals = []
for goal_name in goals:
if not solution.is_goal_satisfied(goal_name):
total_dev = solution.get_total_deviation(goal_name)
if total_dev:
unmet_goals.append((goal_name, total_dev))
# Sort by deviation (largest first)
unmet_goals.sort(key=lambda x: x[1], reverse=True)
print("Unmet Goals (prioritized by deviation):")
for goal_name, deviation in unmet_goals:
print(f" {goal_name}: {deviation:.2f}")
# Show breakdown
deviations = solution.get_goal_deviations(goal_name)
if deviations:
print(f" Positive: {deviations['pos']}")
print(f" Negative: {deviations['neg']}")
Deviation Breakdown¶
For indexed goals, analyze deviations by index:
def analyze_indexed_goal(solution, goal_name):
"""Detailed analysis of indexed goal deviations."""
deviations = solution.get_goal_deviations(goal_name)
if not deviations:
print(f"Goal '{goal_name}' not found")
return
pos_dev = deviations["pos"]
neg_dev = deviations["neg"]
# Check if indexed
if isinstance(pos_dev, dict):
print(f"Goal: {goal_name} (indexed)")
print(f"\n{'Index':<20} {'Pos Deviation':<18} {'Neg Deviation':<18} {'Status'}")
print("-" * 80)
all_keys = set(pos_dev.keys()) | set(neg_dev.keys())
for key in sorted(all_keys):
pos = pos_dev.get(key, 0)
neg = neg_dev.get(key, 0)
if abs(pos) < 1e-6 and abs(neg) < 1e-6:
status = "Satisfied"
elif pos > 1e-6:
status = f"Over by {pos:.2f}"
else:
status = f"Under by {neg:.2f}"
print(f"{str(key):<20} {pos:<18.2f} {neg:<18.2f} {status}")
else:
# Scalar goal
print(f"Goal: {goal_name} (scalar)")
print(f"Positive deviation: {pos_dev:.2f}")
print(f"Negative deviation: {neg_dev:.2f}")
Solution Summary with Goals¶
The summary() method includes goal information:
print(solution.summary())
Output:
Status: optimal
Objective: 12345.678900
Solve time: 0.123s
Non-zero variables: 42/100
Goal Constraints: 5
Goals Satisfied: 3/5
Example Workflows¶
Production Planning with Goals¶
from lumix import LXModel, LXVariable, LXConstraint
# Define variables
production = LXVariable[Product, float]("production").from_data(products)
# Define goals
demand_goal = (
LXConstraint[Product]("demand_goal")
.expression(...)
.eq()
.rhs(lambda p: p.demand_target)
.as_goal(priority=1) # Higher priority
.from_data(products)
)
quality_goal = (
LXConstraint("quality_goal")
.expression(...)
.ge()
.rhs(0.95)
.as_goal(priority=2) # Lower priority
)
# Build and solve
model = LXModel("production_with_goals")
model.add_variable(production)
model.add_constraint(demand_goal)
model.add_constraint(quality_goal)
solution = optimizer.solve(model)
# Analyze results
if solution.is_optimal():
print("=== Goal Achievement ===")
# Check demand goals
if solution.is_goal_satisfied("demand_goal"):
print("✓ All demand targets met")
else:
print("✗ Some demand targets missed")
analyze_indexed_goal(solution, "demand_goal")
# Check quality goal
if solution.is_goal_satisfied("quality_goal"):
print("✓ Quality target met")
else:
deviations = solution.get_goal_deviations("quality_goal")
if deviations["neg"] > 0:
print(f"✗ Quality {deviations['neg']:.2%} below target")
Multi-Objective Optimization¶
def solve_multi_objective(products, priorities):
"""Solve with multiple conflicting objectives."""
# Goals with different priorities
goals = {
"maximize_profit": {
"priority": 1,
"weight": 1.0,
"type": "maximize"
},
"minimize_waste": {
"priority": 2,
"weight": 0.5,
"type": "minimize"
},
"meet_demand": {
"priority": 1,
"weight": 1.0,
"type": "target"
}
}
# Build model with goals...
solution = optimizer.solve(model)
# Analyze trade-offs
print("=== Multi-Objective Results ===")
print(f"Objective value: {solution.objective_value:.2f}")
print()
for goal_name, config in goals.items():
satisfied = solution.is_goal_satisfied(goal_name)
total_dev = solution.get_total_deviation(goal_name)
status = "✓" if satisfied else "✗"
print(f"{status} {goal_name} (priority {config['priority']})")
if not satisfied and total_dev:
print(f" Deviation: {total_dev:.2f}")
print(f" Weight: {config['weight']}")
print(f" Weighted penalty: {total_dev * config['weight']:.2f}")
Sequential Goal Programming¶
def sequential_goal_programming(products, goal_priorities):
"""Solve goals sequentially by priority."""
# Priority levels (1 = highest)
priorities = sorted(set(goal_priorities.values()))
solutions_by_priority = {}
for priority in priorities:
print(f"\\n=== Solving Priority {priority} Goals ===")
# Get goals at this priority
current_goals = [
name for name, p in goal_priorities.items()
if p == priority
]
# Build and solve model for this priority
model = build_model_with_goals(current_goals)
solution = optimizer.solve(model)
solutions_by_priority[priority] = solution
# Report results
for goal_name in current_goals:
satisfied = solution.is_goal_satisfied(goal_name)
status = "✓" if satisfied else "✗"
print(f"{status} {goal_name}")
if not satisfied:
total_dev = solution.get_total_deviation(goal_name)
print(f" Deviation: {total_dev:.2f}")
return solutions_by_priority
Handling Deviation Types¶
Asymmetric Goals¶
Sometimes you only care about one type of deviation:
def check_one_sided_goal(solution, goal_name, direction="under"):
"""Check goals where only one direction matters."""
deviations = solution.get_goal_deviations(goal_name)
if not deviations:
return None
if direction == "under":
# Only care about under-achievement (negative deviation)
total_under = deviations["neg"]
if isinstance(total_under, dict):
total_under = sum(total_under.values())
if total_under < 1e-6:
print(f"✓ {goal_name}: No under-achievement")
else:
print(f"✗ {goal_name}: Under by {total_under:.2f}")
elif direction == "over":
# Only care about over-achievement (positive deviation)
total_over = deviations["pos"]
if isinstance(total_over, dict):
total_over = sum(total_over.values())
if total_over < 1e-6:
print(f"✓ {goal_name}: No over-achievement")
else:
print(f"✗ {goal_name}: Over by {total_over:.2f}")
Weighted Deviations¶
Calculate weighted deviation for cost analysis:
def calculate_weighted_deviation(solution, goal_name, pos_weight, neg_weight):
"""Calculate deviation with asymmetric weights."""
deviations = solution.get_goal_deviations(goal_name)
if not deviations:
return None
pos_dev = deviations["pos"]
neg_dev = deviations["neg"]
# Handle scalar vs indexed
if isinstance(pos_dev, dict):
total_pos = sum(pos_dev.values())
total_neg = sum(neg_dev.values())
else:
total_pos = pos_dev
total_neg = neg_dev
weighted_total = total_pos * pos_weight + total_neg * neg_weight
print(f"{goal_name}:")
print(f" Positive deviation: {total_pos:.2f} × {pos_weight} = {total_pos * pos_weight:.2f}")
print(f" Negative deviation: {total_neg:.2f} × {neg_weight} = {total_neg * neg_weight:.2f}")
print(f" Total weighted deviation: {weighted_total:.2f}")
return weighted_total
Best Practices¶
Always Check Goal Existence
deviations = solution.get_goal_deviations("my_goal") if deviations is None: print("Goal not found") return
Use Appropriate Tolerance
# For percentage goals satisfied = solution.is_goal_satisfied("quality", tolerance=0.01) # 1% # For absolute goals satisfied = solution.is_goal_satisfied("demand", tolerance=1.0) # 1 unit
Handle Both Scalar and Indexed Goals
deviations = solution.get_goal_deviations("my_goal") pos_dev = deviations["pos"] # Check if indexed if isinstance(pos_dev, dict): total = sum(pos_dev.values()) else: total = pos_dev
Report Goal Achievement Clearly
def report_goals(solution, goal_names): achieved = sum(1 for g in goal_names if solution.is_goal_satisfied(g)) total = len(goal_names) pct = (achieved / total) * 100 print(f"Goals Achieved: {achieved}/{total} ({pct:.1f}%)")
Common Patterns¶
Dashboard Summary¶
def goal_programming_dashboard(solution, goals_config):
"""Create dashboard summary of goal achievement."""
print("=" * 80)
print("GOAL PROGRAMMING DASHBOARD".center(80))
print("=" * 80)
by_priority = {}
for goal_name, config in goals_config.items():
priority = config.get("priority", 1)
if priority not in by_priority:
by_priority[priority] = []
by_priority[priority].append(goal_name)
for priority in sorted(by_priority.keys()):
print(f"\\nPriority {priority} Goals:")
print("-" * 80)
for goal_name in by_priority[priority]:
satisfied = solution.is_goal_satisfied(goal_name)
total_dev = solution.get_total_deviation(goal_name)
status_icon = "✓" if satisfied else "✗"
status_text = "Satisfied" if satisfied else f"Deviation: {total_dev:.2f}"
print(f"{status_icon} {goal_name:<40} {status_text}")
print("=" * 80)
Next Steps¶
Accessing Solutions - Learn about accessing solution values
Sensitivity Analysis - Perform sensitivity analysis
Solution Mapping - Map solutions to ORM models
Solution Module API - Full API reference