Tracing Infeasibilities¶
This tutorial demonstrates how to identify and trace infeasibilities in PyPSA optimization models using built-in functionality. When an optimization problem becomes infeasible, PyPSA provides tools to help you understand what constraints are conflicting and causing the infeasibility.
In this example, we'll deliberately create an infeasible network and then use PyPSA's built-in methods to diagnose the problem.
Getting ready¶
First, let's import PyPSA and load a example network from our example suite that we'll use for demonstration.
We'll use PyPSA's built-in AC-DC meshed example network, which includes:
- AC transmission lines connecting different regions
- A DC link providing additional transmission capacity
- Generators and loads across multiple buses
import pypsa
n = pypsa.examples.ac_dc_meshed()
n
INFO:pypsa.network.io:Retrieving network data from https://github.com/PyPSA/PyPSA/raw/v1.0.5/examples/networks/ac-dc-meshed/ac-dc-meshed.nc.
INFO:pypsa.network.io:Imported network 'AC-DC-Meshed' has buses, carriers, generators, global_constraints, lines, links, loads
PyPSA Network 'AC-DC-Meshed' ---------------------------- Components: - Bus: 9 - Carrier: 6 - Generator: 6 - GlobalConstraint: 1 - Line: 7 - Link: 4 - Load: 6 Snapshots: 10
For this demonstration, we'll limit the analysis to just the first snapshot to keep the example simple and focused on the infeasibility tracing concept.
## Solve only the first period
n.snapshots = n.snapshots[:1]
This network is normally feasible, but let's modify it to create infeasibilities:
# Remove AC transmission lines connecting to London
n.remove("Line", "0") # First AC line to London
n.remove("Line", "5") # Second AC line to London
# Disable the DC link by setting capacity to zero
n.links.loc["DC link", "p_nom"] = 0.0
n.links.loc["DC link", "p_nom_extendable"] = False
An alternative approach would be to remove the DC link entirely, which would also create an infeasible situation (London with load but no connections):
n.remove("Link", "DC link")
This would completely remove the DC link from the network. However, when n.optimize() is called, PyPSA would detect an "empty LHS and non-empty RHS" error during the constraint creation phase (specifically when building nodal balance constraints) and raise a ValueError before the optimization model is even passed to the solver.
Let's ensure that London has power load but no generators or connections:
print(f"London load: {n.loads_t.p_set.loc['2015-01-01', 'London']:.1f} MW")
print(
f"London generation: {n.generators[n.generators.bus == 'London']['p_nom'].sum():.1f} MW"
)
print(
f"London AC lines connected: {len(n.lines[(n.lines.bus0 == 'London') | (n.lines.bus1 == 'London')])}"
)
print(f"London DC link capacity: {n.links.loc['DC link', 'p_nom']:.1f} MW")
print("→ Infeasible: London has power load (RHS) but no supply options!")
London load: 35.8 MW London generation: 0.0 MW London AC lines connected: 0 London DC link capacity: 0.0 MW → Infeasible: London has power load (RHS) but no supply options!
Attempting Optimization¶
Now let's try to optimize this infeasible network. The optimization will fail because London has load but no way to meet it (no local generation and no transmission connections)
n.optimize(solver_name="gurobi")
WARNING:pypsa.consistency:The following lines have zero x, which could break the linear load flow: Index(['2', '3', '4'], dtype='object', name='name')
WARNING:pypsa.consistency:The following lines have zero r, which could break the linear load flow: Index(['1', '6'], dtype='object', name='name')
INFO:linopy.model: Solve problem using Gurobi solver
INFO:linopy.io: Writing time: 0.04s
Restricted license - for non-production use only - expires 2026-11-23
INFO:gurobipy:Restricted license - for non-production use only - expires 2026-11-23
Read LP format model from file /tmp/linopy-problem-sa5aaylb.lp
INFO:gurobipy:Read LP format model from file /tmp/linopy-problem-sa5aaylb.lp
Reading time = 0.01 seconds
INFO:gurobipy:Reading time = 0.01 seconds
obj: 55 rows, 30 columns, 96 nonzeros
INFO:gurobipy:obj: 55 rows, 30 columns, 96 nonzeros
Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - "Ubuntu 24.04 LTS")
INFO:gurobipy:Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - "Ubuntu 24.04 LTS")
INFO:gurobipy:
CPU model: AMD EPYC 9R14, instruction set [SSE2|AVX|AVX2|AVX512]
INFO:gurobipy:CPU model: AMD EPYC 9R14, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 2 physical cores, 2 logical processors, using up to 2 threads
INFO:gurobipy:Thread count: 2 physical cores, 2 logical processors, using up to 2 threads
INFO:gurobipy:
Optimize a model with 55 rows, 30 columns and 96 nonzeros
INFO:gurobipy:Optimize a model with 55 rows, 30 columns and 96 nonzeros
Model fingerprint: 0xa9ae711a
INFO:gurobipy:Model fingerprint: 0xa9ae711a
Coefficient statistics:
INFO:gurobipy:Coefficient statistics:
Matrix range [5e-01, 1e+00]
INFO:gurobipy: Matrix range [5e-01, 1e+00]
Objective range [9e-03, 3e+03]
INFO:gurobipy: Objective range [9e-03, 3e+03]
Bounds range [2e+07, 2e+07]
INFO:gurobipy: Bounds range [2e+07, 2e+07]
RHS range [4e+01, 1e+03]
INFO:gurobipy: RHS range [4e+01, 1e+03]
Presolve removed 0 rows and 1 columns
INFO:gurobipy:Presolve removed 0 rows and 1 columns
Presolve time: 0.02s
INFO:gurobipy:Presolve time: 0.02s
INFO:gurobipy:
Solved in 0 iterations and 0.02 seconds (0.00 work units)
INFO:gurobipy:Solved in 0 iterations and 0.02 seconds (0.00 work units)
Infeasible or unbounded model
INFO:gurobipy:Infeasible or unbounded model
INFO:linopy.solvers:Unable to save solution file. Raised error: Unable to retrieve attribute 'X'
WARNING:linopy.constants:Optimization potentially failed: Status: warning Termination condition: infeasible_or_unbounded Solution: 0 primals, 0 duals Objective: nan Solver model: available Solver message: 4
('warning', 'infeasible_or_unbounded')
Tracing the Infeasibility¶
When the optimization fails due to infeasibility, PyPSA provides a convenient method to diagnose the problem. The print_infeasibilities() method on the optimization model will show us exactly which constraints are causing the infeasibility.
n.model.print_infeasibilities()
Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - "Ubuntu 24.04 LTS")
INFO:gurobipy:Gurobi Optimizer version 12.0.3 build v12.0.3rc0 (linux64 - "Ubuntu 24.04 LTS")
INFO:gurobipy:
CPU model: AMD EPYC 9R14, instruction set [SSE2|AVX|AVX2|AVX512]
INFO:gurobipy:CPU model: AMD EPYC 9R14, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 2 physical cores, 2 logical processors, using up to 2 threads
INFO:gurobipy:Thread count: 2 physical cores, 2 logical processors, using up to 2 threads
INFO:gurobipy:
INFO:gurobipy:
IIS computed: 2 constraints and 0 bounds
INFO:gurobipy:IIS computed: 2 constraints and 0 bounds
IIS runtime: 0.00 seconds (0.00 work units)
INFO:gurobipy:IIS runtime: 0.00 seconds (0.00 work units)
Link-fix-p-lower[2015-01-01 00:00:00, DC link]: +1 Link-p[2015-01-01 00:00:00, DC link] ≥ -0 Bus-nodal_balance[London, 2015-01-01 00:00:00]: -1 Link-p[2015-01-01 00:00:00, DC link] = 35.7962441027
Interpreting the Infeasibility Output¶
The infeasibility trace above shows us exactly what's wrong:
Link-fix-p-lower[2015-01-01 00:00:00, DC link]: +1 Link-p[2015-01-01 00:00:00, DC link] ≥ -0- This constraint says the DC link power flow must be ≥ 0 (can't flow negative)
- But we set the DC link capacity to 0, so Link-p value is bounded to 0
Bus-nodal_balance[London, 2015-01-01 00:00:00]: -1 Link-p[2015-01-01 00:00:00, DC link] = 35.7962441027- This is London's nodal balance equation: generation ± trade ± storage = load
- London has no generation, 35.8 MW load, and no AC interconnectors
- The DC link (the only remaining connection) can't provide any power because its capacity is 0
Under the hood, print_infeasibilities() uses Gurobi's infeasibility analysis to identify conflicting constraints.
Irreducible Inconsistent Set (IIS) identified by Gurobi shows these two constraints are fundamentally incompatible:
- London needs power inflow via the DC link to meet its load
- But the DC link is constrained to zero power
- This creates an impossible situation that no solution can satisfy