Goal Programming Module API¶
The goal programming module provides automatic LP-to-Goal Programming conversion with constraint relaxation, deviation variables, and multiple solving modes.
Overview¶
The goal programming module implements a complete goal programming framework that transforms traditional optimization models into multi-objective goal programming models:
graph TD
A[Hard Constraints] --> B[Goal Constraints]
B --> C[Constraint Relaxation]
C --> D[Deviation Variables]
D --> E[Objective Building]
E --> F[Weighted/Sequential Solving]
G[LXGoalMetadata] --> C
H[LXGoalMode] --> F
I[RelaxedConstraint] --> E
style A fill:#e1f5ff
style B fill:#fff4e1
style C fill:#ffe1e1
style D fill:#e1ffe1
style E fill:#f0e1ff
style F fill:#e8f4f8
Key Features¶
Automatic Relaxation: Convert hard constraints to soft constraints with deviation variables
Multiple Modes: Weighted (single solve) and Sequential (lexicographic) goal programming
Priority-Based: Support for multiple priority levels with automatic weight scaling
Custom Objectives: Integrate traditional objectives with goal constraints
Type-Safe: Full type safety with generic data models
Data-Driven: Deviation variables indexed by Goal instances for semantic meaning
Components¶
Goal Metadata and Data Structures¶
Represents a single goal instance in goal programming. |
|
Metadata for a goal constraint in goal programming. |
|
Goal programming solving modes. |
|
Generate standard name for deviation variable. |
|
Convert priority level to weight for weighted goal programming. |
The LXGoal class represents individual goal instances
that serve as the data source for deviation variables. This provides semantic meaning to
deviations (e.g., “Route 5 needs 3 additional buses”).
The LXGoalMetadata class stores goal configuration
including priority, weight, and which deviations are undesired.
The LXGoalMode enum defines solving modes: WEIGHTED
or SEQUENTIAL (lexicographic).
Constraint Relaxation¶
Result of relaxing a constraint for goal programming. |
|
Relax a constraint by adding deviation variables. |
|
Relax multiple constraints for goal programming. |
The relaxation module transforms hard constraints into equality constraints with deviation variables:
LE (≤): expr + neg_dev - pos_dev = rhs (minimize pos_dev)
GE (≥): expr + neg_dev - pos_dev = rhs (minimize neg_dev)
EQ (=): expr + neg_dev - pos_dev = rhs (minimize both)
Objective Building¶
|
Build single weighted objective for goal programming. |
|
Build sequential objectives for lexicographic goal programming. |
Combine a base objective with goal programming objective. |
|
|
Extract constraints marked as custom objectives (priority 0). |
The objective building module constructs goal programming objectives:
Weighted: Single objective with exponentially scaled priorities
Sequential: Multiple objectives solved lexicographically
Combined: Mix traditional objectives with goal deviations
Custom: Extract priority 0 goals as custom objective terms
Solver Orchestration¶
Orchestrates sequential (lexicographic) goal programming. |
|
High-level convenience function for goal programming. |
The LXGoalProgrammingSolver class orchestrates
sequential (lexicographic) goal programming with multiple solve iterations.
For weighted mode, transformation is handled directly in LXModel.
Detailed API Reference¶
Goal Metadata¶
Goal programming metadata and data structures.
- class lumix.goal_programming.goal.LXGoalMode(value)[source]
Bases:
EnumGoal programming solving modes.
- WEIGHTED = 'weighted'
- SEQUENTIAL = 'sequential'
- class lumix.goal_programming.goal.LXGoalMetadata(priority, weight, constraint_sense, undesired_deviations=<factory>)[source]
Bases:
objectMetadata for a goal constraint in goal programming.
A goal constraint is a soft constraint that can be violated with a penalty. The goal is transformed by adding deviation variables:
For LE: expr + neg_dev - pos_dev == rhs (minimize pos_dev)
For GE: expr + neg_dev - pos_dev == rhs (minimize neg_dev)
For EQ: expr + neg_dev - pos_dev == rhs (minimize both)
- Parameters:
priority (int)
weight (float)
constraint_sense (LXConstraintSense)
- priority
Priority level (1=highest, 2=second highest, etc.) Priority 0 is reserved for custom objective terms
- weight
Relative weight within the same priority level
- constraint_sense
Original constraint type (LE, GE, EQ)
- undesired_deviations
Set of deviation types to minimize (‘pos’, ‘neg’, or both)
-
priority:
int
-
weight:
float
-
constraint_sense:
LXConstraintSense
- __post_init__()[source]
Determine undesired deviations based on constraint sense.
- is_custom_objective()[source]
Check if this is a custom objective term (priority 0).
- Return type:
- __init__(priority, weight, constraint_sense, undesired_deviations=<factory>)
- Parameters:
priority (int)
weight (float)
constraint_sense (LXConstraintSense)
- Return type:
None
- class lumix.goal_programming.goal.LXGoal(id, constraint_name, priority, weight, constraint_sense, target_value=None, instance_id=None)[source]
Bases:
objectRepresents a single goal instance in goal programming.
Deviation variables are indexed by Goal instances, making deviations semantically meaningful: they measure achievement of specific goals rather than just constraint satisfaction.
This provides practical business value: - Bus assignment: “Route 5 needs 3 additional buses” (neg_dev) - Production: “Product A has 20 units excess inventory” (pos_dev) - Scheduling: “Department B is 5 hours over overtime limit” (pos_dev)
- Parameters:
- id
Unique identifier for this goal instance
- constraint_name
Name of the original constraint
- priority
Goal priority level (1=highest, 0=custom objective)
- weight
Weight within the same priority level
- constraint_sense
Type of constraint (LE, GE, EQ)
- target_value
Target RHS value if constant
- instance_id
ID of the original constraint instance (if indexed)
Examples
- Single goal (non-indexed constraint):
>>> goal = LXGoal( ... id="total_demand", ... constraint_name="demand_goal", ... priority=1, ... weight=1.0, ... constraint_sense=LXConstraintSense.GE, ... target_value=1000.0 ... )
- Per-product goals (indexed constraint):
>>> goals = [ ... LXGoal( ... id="demand_product_1", ... constraint_name="production_goal", ... priority=1, ... weight=1.0, ... constraint_sense=LXConstraintSense.GE, ... target_value=100.0, ... instance_id=1 ... ), ... LXGoal( ... id="demand_product_2", ... constraint_name="production_goal", ... priority=1, ... weight=1.0, ... constraint_sense=LXConstraintSense.GE, ... target_value=150.0, ... instance_id=2 ... ), ... ]
-
id:
str
-
constraint_name:
str
-
priority:
int
-
weight:
float
-
constraint_sense:
LXConstraintSense
- lumix.goal_programming.goal.get_deviation_var_name(constraint_name, deviation_type)[source]
Generate standard name for deviation variable.
- lumix.goal_programming.goal.priority_to_weight(priority, base=10.0, exponent_offset=6)[source]
Convert priority level to weight for weighted goal programming.
Higher priorities get exponentially larger weights to ensure they dominate lower priorities in the objective function.
- Parameters:
- Return type:
- Returns:
Weight value for the priority level
Examples
>>> priority_to_weight(1) 1000000.0 >>> priority_to_weight(2) 100000.0 >>> priority_to_weight(0) # Custom objectives 1.0
Key Classes:
LXGoal: Represents a single goal instance with metadataLXGoalMetadata: Configuration for goal constraints (priority, weight, sense)LXGoalMode: Enum for solving modes (WEIGHTED, SEQUENTIAL)
Utility Functions:
get_deviation_var_name(): Generate standard deviation variable namespriority_to_weight(): Convert priority levels to exponential weights
Constraint Relaxation¶
Constraint relaxation for goal programming.
- class lumix.goal_programming.relaxation.RelaxedConstraint(constraint, pos_deviation, neg_deviation, goal_metadata, goal_instances)[source]
Bases:
Generic[TModel]Result of relaxing a constraint for goal programming.
Contains the relaxed constraint (now an equality with deviation variables), the deviation variables themselves, and the goal instances that serve as the data source for deviation variables.
- Parameters:
constraint (LXConstraint[TModel])
pos_deviation (LXVariable[LXGoal, float])
neg_deviation (LXVariable[LXGoal, float])
goal_metadata (LXGoalMetadata)
- __init__(constraint, pos_deviation, neg_deviation, goal_metadata, goal_instances)[source]
Initialize relaxed constraint.
- Parameters:
constraint (
LXConstraint[TypeVar(TModel)]) – The relaxed constraint (LHS + neg_dev - pos_dev == RHS)pos_deviation (
LXVariable[LXGoal,float]) – Positive deviation variable family (indexed by Goals)neg_deviation (
LXVariable[LXGoal,float]) – Negative deviation variable family (indexed by Goals)goal_metadata (
LXGoalMetadata) – Goal metadata (priority, weight, undesired deviations)goal_instances (
List[LXGoal]) – Goal instances serving as data source for deviations
- __deepcopy__(memo)[source]
Custom deepcopy that handles constraint and deviation variables.
- Parameters:
memo – Dictionary for tracking circular references during deepcopy
- Returns:
Deep copy of this relaxed constraint
- get_undesired_variables()[source]
Get list of undesired deviation variables to minimize.
- Return type:
- Returns:
List of deviation variables to include in objective
- lumix.goal_programming.relaxation.relax_constraint(constraint, goal_metadata)[source]
Relax a constraint by adding deviation variables.
Transforms the constraint based on its type: - LE (expr <= rhs): expr + neg_dev - pos_dev == rhs (minimize pos_dev) - GE (expr >= rhs): expr + neg_dev - pos_dev == rhs (minimize neg_dev) - EQ (expr == rhs): expr + neg_dev - pos_dev == rhs (minimize both)
- Parameters:
constraint (
LXConstraint[TypeVar(TModel)]) – Original goal constraint to relaxgoal_metadata (
LXGoalMetadata) – Goal metadata with priority and weight info
- Return type:
RelaxedConstraint[TypeVar(TModel)]- Returns:
RelaxedConstraint containing the equality constraint with deviations and the deviation variable families
- Raises:
ValueError – If constraint has no LHS expression
Example
>>> goal = LXConstraint[Product]("production_goal") ... .expression(production_expr) ... .ge() ... .rhs(lambda p: p.target) >>> relaxed = relax_constraint(goal, goal_metadata) >>> # Now: production_expr + neg_dev - pos_dev == target >>> # Objective: minimize neg_dev (under-production is bad for GE)
- lumix.goal_programming.relaxation.relax_constraints(constraints, goal_metadata_map)[source]
Relax multiple constraints for goal programming.
- Parameters:
constraints (
List[LXConstraint[TypeVar(TModel)]]) – List of constraints to relaxgoal_metadata_map (
Dict[str,LXGoalMetadata]) – Mapping from constraint name to goal metadata
- Return type:
List[RelaxedConstraint[TypeVar(TModel)]]- Returns:
List of relaxed constraints with their deviation variables
Example
>>> constraints = [goal1, goal2, goal3] >>> metadata = { ... "goal1": LXGoalMetadata(priority=1, weight=1.0, ...), ... "goal2": LXGoalMetadata(priority=2, weight=0.5, ...), ... } >>> relaxed = relax_constraints(constraints, metadata)
Key Classes:
RelaxedConstraint: Container for relaxed constraint with deviation variables
Key Functions:
relax_constraint(): Relax a single constraintrelax_constraints(): Batch relaxation of multiple constraints
Objective Building¶
Objective function construction for goal programming.
- lumix.goal_programming.objective_builder.build_weighted_objective(relaxed_constraints, base=10.0, exponent_offset=6)[source]
Build single weighted objective for goal programming.
Combines all priorities into one objective using exponentially decreasing weights to ensure higher priorities dominate lower priorities.
Objective: minimize sum(priority_weight[p] * goal_weight[g] * deviation[g])
- Parameters:
relaxed_constraints (
List[RelaxedConstraint]) – List of relaxed constraints with deviation variablesbase (
float) – Base for exponential priority-to-weight conversion (default: 10)exponent_offset (
int) – Offset for exponent (default: 6, so P1=10^6, P2=10^5)
- Return type:
- Returns:
Linear expression for weighted goal programming objective
Example
Priority 1 goals get weight 10^6, priority 2 get 10^5, etc. This ensures P1 goals are effectively optimized first, then P2, etc.
- lumix.goal_programming.objective_builder.build_sequential_objectives(relaxed_constraints)[source]
Build sequential objectives for lexicographic goal programming.
Creates one objective per priority level. These are solved sequentially: 1. Solve priority 1, record optimal deviation values 2. Fix priority 1 deviations, solve priority 2 3. Continue for all priorities
- Parameters:
relaxed_constraints (
List[RelaxedConstraint]) – List of relaxed constraints with deviation variables- Return type:
- Returns:
List of (priority, objective_expression) tuples, sorted by priority
Example
>>> objectives = build_sequential_objectives(relaxed) >>> # [(1, expr_p1), (2, expr_p2), (3, expr_p3)] >>> # Solve P1 first, then P2, then P3
- lumix.goal_programming.objective_builder.combine_objectives(base_objective, goal_objective, goal_weight=1.0)[source]
Combine a base objective with goal programming objective.
This is useful when you have a primary objective function (e.g., maximize profit) and also want to incorporate goal constraints.
- Parameters:
base_objective (
LXLinearExpression) – Primary objective expressiongoal_objective (
LXLinearExpression) – Goal programming objective (sum of weighted deviations)goal_weight (
float) – Relative weight for goal objective (default: 1.0)
- Return type:
- Returns:
Combined objective expression
Example
>>> profit_expr = LXLinearExpression().add_term(profit, 1.0) >>> goal_expr = build_weighted_objective(relaxed_constraints) >>> # Combine: maximize profit - goal_deviations >>> combined = combine_objectives(profit_expr, goal_expr, goal_weight=0.1)
- lumix.goal_programming.objective_builder.extract_custom_objectives(relaxed_constraints)[source]
Extract constraints marked as custom objectives (priority 0).
These represent user-defined objective terms that should be incorporated into the optimization alongside goal deviations.
- Parameters:
relaxed_constraints (
List[RelaxedConstraint]) – List of all relaxed constraints- Return type:
- Returns:
List of relaxed constraints with priority 0
Key Functions:
build_weighted_objective(): Build single weighted objectivebuild_sequential_objectives(): Build objectives for sequential solvingcombine_objectives(): Combine traditional and goal objectivesextract_custom_objectives(): Extract priority 0 custom objectives
Solver¶
Goal programming solver orchestration.
- class lumix.goal_programming.solver.LXGoalProgrammingSolver(optimizer)[source]
Bases:
objectOrchestrates sequential (lexicographic) goal programming.
For weighted mode, the model transformation is handled directly in LXModel. This solver is specifically for sequential mode, which requires multiple solve iterations.
Example
>>> solver = LXGoalProgrammingSolver(optimizer) >>> solution = solver.solve_sequential(model, relaxed_constraints)
- Parameters:
optimizer (LXOptimizer)
- __init__(optimizer)[source]
Initialize goal programming solver.
- Parameters:
optimizer (
LXOptimizer) – LXOptimizer instance configured with solver
- solve_sequential(model, relaxed_constraints, **solver_params)[source]
Solve using sequential/lexicographic goal programming.
Solves one priority level at a time: 1. Solve priority 1, record optimal deviation values 2. Add constraints fixing priority 1 deviations to optimal values 3. Solve priority 2 with fixed priority 1 4. Repeat for all priorities
- Parameters:
- Return type:
LXSolution[TypeVar(TModel)]- Returns:
Final solution with all priorities optimized sequentially
- Raises:
ValueError – If no objectives can be built from relaxed constraints
- solve_weighted(model, **solver_params)[source]
Solve using weighted goal programming.
This is a simple pass-through to the standard optimizer, since the weighted objective is already built into the model.
- Parameters:
- Return type:
LXSolution[TypeVar(TModel)]- Returns:
Solution from single optimization
- lumix.goal_programming.solver.solve_goal_programming(model, optimizer, mode=LXGoalMode.WEIGHTED, **solver_params)[source]
High-level convenience function for goal programming.
- Parameters:
model (
LXModel[TypeVar(TModel)]) – LXModel with goal constraints (marked with .as_goal())optimizer (
LXOptimizer) – Configured optimizermode (
LXGoalMode) – Goal programming mode (WEIGHTED or SEQUENTIAL)**solver_params (
Any) – Additional solver parameters
- Return type:
LXSolution[TypeVar(TModel)]- Returns:
Solution based on selected mode
Example
>>> model = LXModel("production") >>> model.add_constraint( ... LXConstraint("demand_goal") ... .expression(production_expr) ... .ge() ... .rhs(1000) ... .as_goal(priority=1, weight=1.0) ... ) >>> optimizer = LXOptimizer().use_solver("gurobi") >>> solution = solve_goal_programming(model, optimizer, mode=LXGoalMode.WEIGHTED)
Key Classes:
LXGoalProgrammingSolver: Orchestrates sequential goal programming
Key Functions:
solve_goal_programming(): High-level convenience function
Usage Examples¶
Basic Weighted Goal Programming¶
from lumix import LXModel, LXVariable, LXConstraint, LXOptimizer
from lumix.core.expressions import LXLinearExpression
# Define variables
production = LXVariable[Product, float]("production").from_data(products)
# Define goal constraint
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 model (goal mode is set by as_goal())
model = LXModel("production")
model.add_variable(production)
model.add_constraint(demand_goal)
# Solve
optimizer = LXOptimizer().use_solver("gurobi")
solution = optimizer.solve(model)
# Access goal deviations
deviations = solution.get_goal_deviations("demand_goal")
print(f"Positive deviation: {deviations['pos']}")
print(f"Negative deviation: {deviations['neg']}")
Multi-Priority Goals¶
# Priority 1: Maximize profit (custom objective)
profit_goal = (
LXConstraint("profit")
.expression(profit_expr)
.ge()
.rhs(0)
.as_goal(priority=0, weight=1.0) # Priority 0 = custom objective
)
# Priority 2: Meet demand (higher priority)
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 3: Minimize overtime (lower priority)
overtime_goal = (
LXConstraint("overtime")
.expression(overtime_expr)
.le()
.rhs(40)
.as_goal(priority=2, weight=0.5)
)
model = (
LXModel("multi_priority")
.add_variable(production)
.add_constraint(profit_goal)
.add_constraint(demand_goal)
.add_constraint(overtime_goal)
)
solution = optimizer.solve(model)
Direct Relaxation API¶
from lumix.goal_programming import relax_constraint, LXGoalMetadata
from lumix.core.enums import LXConstraintSense
# Create goal metadata
metadata = LXGoalMetadata(
priority=1,
weight=1.0,
constraint_sense=LXConstraintSense.GE
)
# Relax constraint
relaxed = relax_constraint(demand_constraint, metadata)
# Access components
equality_constraint = relaxed.constraint # Now EQ with deviations
pos_deviation = relaxed.pos_deviation # LXVariable[LXGoal, float]
neg_deviation = relaxed.neg_deviation # LXVariable[LXGoal, float]
goals = relaxed.goal_instances # List[LXGoal]
Sequential Goal Programming¶
from lumix.goal_programming import LXGoalProgrammingSolver
# Set sequential mode
model.set_goal_mode("sequential")
# Create solver
gp_solver = LXGoalProgrammingSolver(optimizer)
# Get relaxed constraints from model
relaxed_constraints = model._relaxed_constraints
# Solve sequentially by priority
solution = gp_solver.solve_sequential(model, relaxed_constraints)
Building Custom Objectives¶
from lumix.goal_programming import (
build_weighted_objective,
build_sequential_objectives,
combine_objectives
)
# Build weighted objective from relaxed constraints
goal_objective = build_weighted_objective(relaxed_constraints)
# Combine with traditional objective
combined = combine_objectives(
base_objective=profit_expr,
goal_objective=goal_objective,
goal_weight=0.1
)
# Or build sequential objectives
sequential_objs = build_sequential_objectives(relaxed_constraints)
# Returns: [(priority_1, expr_1), (priority_2, expr_2), ...]
Type Hints¶
The goal programming module is fully type-annotated:
from typing import List, Dict, Tuple, Optional
from lumix.goal_programming import (
LXGoal,
LXGoalMetadata,
LXGoalMode,
RelaxedConstraint,
LXGoalProgrammingSolver
)
from lumix.core import LXConstraint, LXVariable
from lumix.core.expressions import LXLinearExpression
# Type-safe goal creation
goal: LXGoal = LXGoal(
id="demand_product_1",
constraint_name="demand",
priority=1,
weight=1.0,
constraint_sense=LXConstraintSense.GE,
target_value=100.0,
instance_id=1
)
# Type-safe relaxation
relaxed: RelaxedConstraint[Product] = relax_constraint(
constraint=demand_constraint,
goal_metadata=metadata
)
# Type-safe solver
solver: LXGoalProgrammingSolver = LXGoalProgrammingSolver(optimizer)
solution: LXSolution[Product] = solver.solve_weighted(model)
See Also¶
Core Module API - Core module (constraints, variables, models)
Solution Module API - Solution module (accessing goal deviations)
Goal Programming Solutions - Working with goal programming solutions