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

lumix.goal_programming.goal.LXGoal

Represents a single goal instance in goal programming.

lumix.goal_programming.goal.LXGoalMetadata

Metadata for a goal constraint in goal programming.

lumix.goal_programming.goal.LXGoalMode

Goal programming solving modes.

lumix.goal_programming.goal.get_deviation_var_name

Generate standard name for deviation variable.

lumix.goal_programming.goal.priority_to_weight

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

lumix.goal_programming.relaxation.RelaxedConstraint

Result of relaxing a constraint for goal programming.

lumix.goal_programming.relaxation.relax_constraint

Relax a constraint by adding deviation variables.

lumix.goal_programming.relaxation.relax_constraints

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

lumix.goal_programming.objective_builder.build_weighted_objective

Build single weighted objective for goal programming.

lumix.goal_programming.objective_builder.build_sequential_objectives

Build sequential objectives for lexicographic goal programming.

lumix.goal_programming.objective_builder.combine_objectives

Combine a base objective with goal programming objective.

lumix.goal_programming.objective_builder.extract_custom_objectives

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

lumix.goal_programming.solver.LXGoalProgrammingSolver

Orchestrates sequential (lexicographic) goal programming.

lumix.goal_programming.solver.solve_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: Enum

Goal programming solving modes.

WEIGHTED = 'weighted'
SEQUENTIAL = 'sequential'
class lumix.goal_programming.goal.LXGoalMetadata(priority, weight, constraint_sense, undesired_deviations=<factory>)[source]

Bases: object

Metadata 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

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
undesired_deviations: Set[str]
__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:

bool

is_pos_undesired()[source]

Check if positive deviation should be minimized.

Return type:

bool

is_neg_undesired()[source]

Check if negative deviation should be minimized.

Return type:

bool

__init__(priority, weight, constraint_sense, undesired_deviations=<factory>)
Parameters:
Return type:

None

class lumix.goal_programming.goal.LXGoal(id, constraint_name, priority, weight, constraint_sense, target_value=None, instance_id=None)[source]

Bases: object

Represents 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
target_value: Optional[float] = None
instance_id: Optional[Any] = None
__init__(id, constraint_name, priority, weight, constraint_sense, target_value=None, instance_id=None)
Parameters:
Return type:

None

lumix.goal_programming.goal.get_deviation_var_name(constraint_name, deviation_type)[source]

Generate standard name for deviation variable.

Parameters:
  • constraint_name (str) – Name of the goal constraint

  • deviation_type (str) – Either ‘pos’ or ‘neg’

Return type:

str

Returns:

Standard deviation variable name

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:
  • priority (int) – Priority level (1=highest, 2=second, etc.)

  • base (float) – Base for exponential scaling (default: 10)

  • exponent_offset (int) – Offset for exponent calculation (default: 6) Priority 1 → 10^6, Priority 2 → 10^5, etc.

Return type:

float

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 metadata

  • LXGoalMetadata: Configuration for goal constraints (priority, weight, sense)

  • LXGoalMode: Enum for solving modes (WEIGHTED, SEQUENTIAL)

Utility Functions:

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:
__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:

List[LXVariable[LXGoal, float]]

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:
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:
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:

Key Functions:

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 variables

  • base (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:

LXLinearExpression

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:

List[Tuple[int, LXLinearExpression]]

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 expression

  • goal_objective (LXLinearExpression) – Goal programming objective (sum of weighted deviations)

  • goal_weight (float) – Relative weight for goal objective (default: 1.0)

Return type:

LXLinearExpression

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:

List[RelaxedConstraint]

Returns:

List of relaxed constraints with priority 0

Key Functions:

Solver

Goal programming solver orchestration.

class lumix.goal_programming.solver.LXGoalProgrammingSolver(optimizer)[source]

Bases: object

Orchestrates 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:
  • model (LXModel[TypeVar(TModel)]) – LXModel with relaxed goal constraints already added

  • relaxed_constraints (List[RelaxedConstraint[TypeVar(TModel)]]) – List of relaxed constraints with deviations

  • **solver_params (Any) – Additional solver 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:
  • model (LXModel[TypeVar(TModel)]) – LXModel with weighted goal objective already set

  • **solver_params (Any) – Additional solver 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 optimizer

  • mode (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:

Key Functions:

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