Optimization Constraints¶
By default, optimex minimizes the environmental impact of a specified category while meeting the time-specific demand \(\mathbf{f}_t\) (see Theory: Complete Formulation). Real-world optimization problems often require additional constraints to reflect practical limitations, policy requirements, or scenario-specific conditions — such as existing brownfield capacities, resource bottlenecks, or cumulative environmental budgets.
This page documents all available constraint options in optimex.
Overview¶
Constraints in optimex are specified through the OptimizationModelInputs object. After creating your model inputs from the LCA processor, you can add constraints before creating the optimization model:
from optimex import converter, optimizer
# Parse LCA data
manager = converter.ModelInputManager()
model_inputs = manager.parse_from_lca_processor(lca_data_processor)
# Add constraints (examples below)
model_inputs.cumulative_process_limits_max = {"ProcessA": 100.0}
model_inputs.category_impact_limits = {("climate_change", 2030): 1000.0}
# Create and solve the model
model = optimizer.create_model(model_inputs, name="my_model", objective_category="climate_change")
solved_model, objective, results = optimizer.solve_model(model)
Process Deployment Limits¶
Control how much capacity can be installed for each process.
Time-Specific Deployment Limits¶
Limit the capacity installed for a specific process at a specific year.
| Field | Type | Description |
|---|---|---|
process_deployment_limits_max |
Dict[Tuple[str, int], float] |
Upper bound on (process, year) installation |
process_deployment_limits_min |
Dict[Tuple[str, int], float] |
Lower bound on (process, year) installation |
Example: Limit new solar capacity to 50 MW in 2025
model_inputs.process_deployment_limits_max = {
("solar_pv", 2025): 50.0,
("solar_pv", 2026): 75.0,
("solar_pv", 2027): 100.0,
}
Example: Require at least 10 MW of wind installation in 2030
Cumulative Deployment Limits¶
Limit the total capacity installed for a process across all time periods.
| Field | Type | Description |
|---|---|---|
cumulative_process_limits_max |
Dict[str, float] |
Maximum total installation for a process |
cumulative_process_limits_min |
Dict[str, float] |
Minimum total installation for a process |
Example: Cap total nuclear capacity at 500 MW
Example: Require at least 200 MW total renewable capacity
Process Operation Limits¶
Control how much a process can operate in each time period.
| Field | Type | Description |
|---|---|---|
process_operation_limits_max |
Dict[Tuple[str, int], float] |
Upper bound on (process, year) operation |
process_operation_limits_min |
Dict[Tuple[str, int], float] |
Lower bound on (process, year) operation |
Operation vs Installation
Operation limits constrain how much of the installed capacity is actually used in each period. A process can only operate up to its installed capacity, but operation limits can further restrict this.
Example: Limit coal plant operation during phase-out
model_inputs.process_operation_limits_max = {
("coal_plant", 2025): 80.0, # Max 80% of capacity
("coal_plant", 2030): 50.0, # Max 50% of capacity
("coal_plant", 2035): 0.0, # Complete phase-out
}
Example: Ensure minimum baseload operation
model_inputs.process_operation_limits_min = {
("nuclear_plant", 2025): 50.0, # Minimum 50 MW operation
("nuclear_plant", 2026): 50.0,
}
Category Impact Limits¶
Constrain the environmental impact in specific categories, either at specific times or cumulatively.
Time-Specific Impact Limits¶
Limit the impact in a category at a specific year.
| Field | Type | Description |
|---|---|---|
category_impact_limits |
Dict[Tuple[str, int], float] |
Upper bound on (category, year) impact |
Example: Annual carbon budget
model_inputs.category_impact_limits = {
("climate_change", 2025): 1000000.0, # 1 Mt CO2-eq in 2025
("climate_change", 2030): 500000.0, # 0.5 Mt CO2-eq in 2030
("climate_change", 2035): 100000.0, # 0.1 Mt CO2-eq in 2035
}
Example: Limit land use in specific years
model_inputs.category_impact_limits = {
("land_use", 2025): 5000.0, # Max 5000 m² in 2025
("land_use", 2030): 4000.0, # Max 4000 m² in 2030
}
Cumulative Impact Limits¶
Limit the total impact across all time periods.
| Field | Type | Description |
|---|---|---|
cumulative_category_impact_limits |
Dict[str, float] |
Upper bound on total impact in a category |
Example: Total carbon budget
model_inputs.cumulative_category_impact_limits = {
"climate_change": 5000000.0, # 5 Mt CO2-eq total budget
}
Example: Multi-category constraints
model_inputs.cumulative_category_impact_limits = {
"climate_change": 5000000.0,
"water_use": 1000000.0,
"land_use": 50000.0,
}
Combining Time-Specific and Cumulative Limits
You can use both types of limits simultaneously. For example, set annual carbon budgets with category_impact_limits while also enforcing a total budget with cumulative_category_impact_limits.
Flow Limits¶
Constrain specific material or energy flows in the system.
Time-Specific Flow Limits¶
Limit flows at specific times. Flows can be products, intermediate flows, or elementary flows.
| Field | Type | Description |
|---|---|---|
flow_limits_max |
Dict[Tuple[str, int], float] |
Upper bound on (flow, year) |
flow_limits_min |
Dict[Tuple[str, int], float] |
Lower bound on (flow, year) |
Example: Limit natural gas consumption
model_inputs.flow_limits_max = {
("natural_gas", 2025): 1000.0, # Max 1000 units in 2025
("natural_gas", 2030): 500.0, # Max 500 units in 2030
}
Example: Require minimum hydrogen production
Example: Limit CO2 emissions per year
Cumulative Flow Limits¶
Limit the total flow across all time periods.
| Field | Type | Description |
|---|---|---|
cumulative_flow_limits_max |
Dict[str, float] |
Upper bound on total flow |
cumulative_flow_limits_min |
Dict[str, float] |
Lower bound on total flow |
Example: Total resource budget
model_inputs.cumulative_flow_limits_max = {
"rare_earth_elements": 10000.0, # Limited total availability
}
Process Coupling¶
Link the deployment of related processes with fixed ratios.
| Field | Type | Description |
|---|---|---|
process_coupling |
Dict[Tuple[str, str], float] |
Ratio constraint: process1 = ratio * process2 |
Example: Coupled storage and generation
# For every 4 MW of solar, require 1 MW of battery storage
model_inputs.process_coupling = {
("battery_storage", "solar_pv"): 0.25, # storage = 0.25 * solar
}
Example: Backup capacity requirements
# Gas backup must be at least 50% of wind capacity
model_inputs.process_coupling = {
("gas_peaker", "wind_turbine"): 0.5,
}
Coupling Direction
The coupling constraint enforces installation[process1] = ratio * installation[process2]. Make sure to set up the ratio in the correct direction.
Existing Capacity (Brownfield)¶
Model systems with pre-existing infrastructure that was installed before the optimization horizon.
| Field | Type | Description |
|---|---|---|
existing_capacity |
Dict[Tuple[str, int], float] |
Capacity at (process, installation_year) |
Brownfield vs Greenfield
- Greenfield: Optimization starts from scratch with no existing capacity
- Brownfield: Some capacity already exists from previous investments
Example: Existing power plant fleet
model_inputs.existing_capacity = {
("coal_plant", 2010): 500.0, # 500 MW installed in 2010
("gas_plant", 2015): 300.0, # 300 MW installed in 2015
("nuclear_plant", 2005): 1000.0, # 1000 MW installed in 2005
}
Key characteristics of existing capacity:
-
Installation year must be before the optimization horizon: If your
SYSTEM_TIMEstarts at 2020, existing capacity must have installation years before 2020. -
Installation impacts are excluded: The environmental impacts of building existing capacity are considered "sunk costs" and not counted in the optimization.
-
Operation impacts are included: When existing capacity operates during the optimization horizon, its operational emissions are counted.
-
Capacity retirement: Existing capacity is subject to the same lifetime constraints as new capacity. It will retire based on the process's operation time limits.
Combining Constraints¶
Multiple constraints can be combined to model complex scenarios:
from optimex import converter, optimizer
# Parse LCA data
manager = converter.ModelInputManager()
model_inputs = manager.parse_from_lca_processor(lca_data_processor)
# Scenario: Rapid decarbonization with practical limits
model_inputs.cumulative_category_impact_limits = {
"climate_change": 5000000.0, # Total carbon budget
}
model_inputs.category_impact_limits = {
("climate_change", 2030): 800000.0, # Annual limit in 2030
("climate_change", 2040): 200000.0, # Annual limit in 2040
}
model_inputs.process_deployment_limits_max = {
("nuclear_plant", 2025): 0.0, # No new nuclear until 2026
("nuclear_plant", 2026): 100.0,
("nuclear_plant", 2027): 200.0,
}
model_inputs.cumulative_process_limits_max = {
"coal_plant": 0.0, # No new coal plants
}
model_inputs.existing_capacity = {
("coal_plant", 2000): 2000.0, # Existing coal to phase out
("gas_plant", 2010): 1000.0,
}
# Create and solve
model = optimizer.create_model(
model_inputs,
name="decarbonization_scenario",
objective_category="climate_change"
)
solved_model, objective, results = optimizer.solve_model(model)
Constraint Summary Table¶
| Constraint Type | Time-Specific | Cumulative |
|---|---|---|
| Process Deployment | process_deployment_limits_max/min |
cumulative_process_limits_max/min |
| Process Operation | process_operation_limits_max/min |
— |
| Category Impact | category_impact_limits |
cumulative_category_impact_limits |
| Flow Limits | flow_limits_max/min |
cumulative_flow_limits_max/min |
| Process Coupling | process_coupling |
— |
| Existing Capacity | existing_capacity |
— |
Handling Infeasibility¶
If constraints are too restrictive, the optimization may become infeasible (no solution exists). To debug:
# Enable IIS (Irreducible Infeasible Set) generation
solved_model, objective, results = optimizer.solve_model(
model,
compute_iis=True
)
# Check model_iis.ilp for the conflicting constraints
Common Causes of Infeasibility
- Demand cannot be met with available capacity limits
- Impact limits are below the minimum achievable impact
- Conflicting min/max constraints
- Insufficient existing capacity with restrictive deployment limits