This section covers how to run optimizations in flixOpt, including different optimization modes and solver configuration.
Before running an optimization, it's helpful to visualize your system structure:
# Generate an interactive network diagram
flow_system.topology.plot(path='my_system.html')
# Or get structure info programmatically
nodes, edges = flow_system.topology.infos()
print(f"Components: {[n for n, d in nodes.items() if d['class'] == 'Component']}")
print(f"Buses: {[n for n, d in nodes.items() if d['class'] == 'Bus']}")
print(f"Flows: {list(edges.keys())}")The recommended way to run an optimization is directly on the FlowSystem:
import flixopt as fx
# Simple one-liner
flow_system.optimize(fx.solvers.HighsSolver())
# Access results directly
print(flow_system.solution['Boiler(Q_th)|flow_rate'])
print(flow_system.components['Boiler'].solution)For more control over the optimization process, you can split model building and solving:
# Build the model first
flow_system.build_model()
# Optionally inspect or modify the model
print(flow_system.model.constraints)
# Then solve
flow_system.solve(fx.solvers.HighsSolver())Best for:
- Small to medium problems
- When you need the globally optimal solution
- Problems without time-coupling simplifications
For large problems, use time series clustering to reduce computational complexity:
# Define clustering parameters
params = fx.ClusteringParameters(
hours_per_period=24, # Hours per typical period
nr_of_periods=8, # Number of typical periods
fix_storage_flows=True,
aggregate_data_and_fix_non_binary_vars=True,
)
# Create clustered FlowSystem
clustered_fs = flow_system.transform.cluster(params)
# Optimize the clustered system
clustered_fs.optimize(fx.solvers.HighsSolver())
# Access results - same structure as original
print(clustered_fs.solution)Best for:
- Investment planning problems
- Year-long optimizations
- When computational speed is critical
Trade-offs:
- Much faster solve times
- Approximates the full problem
- Best when patterns repeat (e.g., typical days)
| Mode | Problem Size | Solve Time | Solution Quality |
|---|---|---|---|
| Standard | Small-Medium | Slow | Optimal |
| Clustered | Very Large | Fast | Approximate |
flixOpt is built on linopy, allowing you to add custom constraints beyond what's available through the standard API.
To add custom constraints, build the model first, then access the underlying linopy model:
# Build the model (without solving)
flow_system.build_model()
# Access the linopy model
model = flow_system.model
# Access variables from the solution namespace
# Variables are named: "ElementLabel|variable_name"
boiler_flow = model.variables['Boiler(Q_th)|flow_rate']
chp_flow = model.variables['CHP(Q_th)|flow_rate']
# Add a custom constraint: Boiler must produce at least as much as CHP
model.add_constraints(
boiler_flow >= chp_flow,
name='boiler_min_chp'
)
# Solve with the custom constraint
flow_system.solve(fx.solvers.HighsSolver())Minimum runtime constraint:
# Require component to run at least 100 hours total
on_var = model.variables['CHP|on'] # Binary on/off variable
hours = flow_system.hours_per_timestep
model.add_constraints(
(on_var * hours).sum() >= 100,
name='chp_min_runtime'
)Linking flows across components:
# Heat pump and boiler combined must meet minimum base load
hp_flow = model.variables['HeatPump(Q_th)|flow_rate']
boiler_flow = model.variables['Boiler(Q_th)|flow_rate']
model.add_constraints(
hp_flow + boiler_flow >= 50, # At least 50 kW combined
name='min_heat_supply'
)Seasonal constraints:
import pandas as pd
# Different constraints for summer vs winter
summer_mask = flow_system.timesteps.month.isin([6, 7, 8])
winter_mask = flow_system.timesteps.month.isin([12, 1, 2])
flow_var = model.variables['Boiler(Q_th)|flow_rate']
# Lower capacity in summer
model.add_constraints(
flow_var.sel(time=flow_system.timesteps[summer_mask]) <= 100,
name='summer_limit'
)Before adding constraints, inspect available variables and existing constraints:
flow_system.build_model()
model = flow_system.model
# List all variables
print(model.variables)
# List all constraints
print(model.constraints)
# Get details about a specific variable
print(model.variables['Boiler(Q_th)|flow_rate'])Variables follow this naming pattern:
| Element Type | Pattern | Example |
|---|---|---|
| Flow rate | Component(FlowLabel)|flow_rate |
Boiler(Q_th)|flow_rate |
| Flow size | Component(FlowLabel)|size |
Boiler(Q_th)|size |
| On/off status | Component|on |
CHP|on |
| Charge state | Storage|charge_state |
Battery|charge_state |
| Effect totals | effect_name|total |
costs|total |
| Solver | Type | Speed | License |
|---|---|---|---|
| HiGHS | Open-source | Fast | Free |
| Gurobi | Commercial | Fastest | Academic/Commercial |
| CPLEX | Commercial | Fastest | Academic/Commercial |
| GLPK | Open-source | Slower | Free |
Recommendation: Start with HiGHS (included by default). Use Gurobi/CPLEX for large models or when speed matters.
# Basic usage with defaults
flow_system.optimize(fx.solvers.HighsSolver())
# With custom options
flow_system.optimize(
fx.solvers.GurobiSolver(
time_limit_seconds=3600,
mip_gap=0.01,
extra_options={
'Threads': 4,
'Presolve': 2
}
)
)Common solver parameters:
time_limit_seconds- Maximum solve timemip_gap- Acceptable optimality gap (0.01 = 1%)log_to_console- Show solver output
- Use longer timesteps where acceptable
- Use
ClusteredOptimizationfor long horizons - Remove unnecessary components
- Simplify constraint formulations
- Enable presolve and cuts
- Adjust optimality tolerances for faster (approximate) solutions
- Use parallel threads when available
- Avoid unnecessary binary variables
- Use continuous investment sizes when possible
- Tighten variable bounds
- Remove redundant constraints
If your model has no feasible solution:
-
Enable excess penalties on buses to allow balance violations:
# Allow imbalance with high penalty cost (default is 1e5) heat_bus = fx.Bus('Heat', excess_penalty_per_flow_hour=1e5) # Or disable penalty to enforce strict balance electricity_bus = fx.Bus('Electricity', excess_penalty_per_flow_hour=None)
When
excess_penalty_per_flow_houris set, the optimization can violate bus balance constraints by paying a penalty, helping identify which constraints cause infeasibility. -
Use Gurobi for infeasibility analysis - When using GurobiSolver and the model is infeasible, flixOpt automatically extracts and logs the Irreducible Inconsistent Subsystem (IIS):
# Gurobi provides detailed infeasibility analysis flow_system.optimize(fx.solvers.GurobiSolver()) # If infeasible, check the model documentation file for IIS details
The infeasible constraints are saved to the model documentation file in the results folder.
-
Check balance constraints - can supply meet demand?
-
Verify capacity limits are consistent
-
Review storage state requirements
-
Simplify model to isolate the issue
See Troubleshooting for more details.
If solutions don't match expectations:
- Verify input data (units, scales)
- Enable logging:
fx.CONFIG.exploring() - Visualize intermediate results
- Start with a simpler model
- Check constraint formulations
- See [Examples](../../examples/03-Optimization Modes.md) for working code
- Learn about Mathematical Notation
- Explore Recipes for common patterns