Note
You can download this example as a Jupyter notebook or start it in interactive mode.
Unit commitment#
This tutorial runs through examples of unit commitment for generators at a single bus. Examples of minimum part-load, minimum up time, minimum down time, start up costs, shut down costs and ramp rate restrictions are shown.
To enable unit commitment on a generator, set its attribute committable = True.
[1]:
import pypsa
import pandas as pd
Minimum part load demonstration#
In final hour load goes below part-load limit of coal gen (30%), forcing gas to commit.
[2]:
nu = pypsa.Network(snapshots=range(4))
nu.add("Bus", "bus")
nu.add(
"Generator",
"coal",
bus="bus",
committable=True,
p_min_pu=0.3,
marginal_cost=20,
p_nom=10000,
)
nu.add(
"Generator",
"gas",
bus="bus",
committable=True,
marginal_cost=70,
p_min_pu=0.1,
p_nom=1000,
)
nu.add("Load", "load", bus="bus", p_set=[4000, 6000, 5000, 800])
[3]:
nu.lopf()
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
INFO:pypsa.linopf:Total preparation time: 0.07s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 3.56e+05
[3]:
('ok', 'optimal')
[4]:
nu.generators_t.status
[4]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 1.0 | 0.0 |
1 | 1.0 | 0.0 |
2 | 1.0 | 0.0 |
3 | 0.0 | 1.0 |
[5]:
nu.generators_t.p
[5]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 4000.0 | 0.0 |
1 | 6000.0 | 0.0 |
2 | 5000.0 | 0.0 |
3 | 0.0 | 800.0 |
Minimum up time demonstration#
Gas has minimum up time, forcing it to be online longer
[6]:
nu = pypsa.Network(snapshots=range(4))
nu.add("Bus", "bus")
nu.add(
"Generator",
"coal",
bus="bus",
committable=True,
p_min_pu=0.3,
marginal_cost=20,
p_nom=10000,
)
nu.add(
"Generator",
"gas",
bus="bus",
committable=True,
marginal_cost=70,
p_min_pu=0.1,
up_time_before=0,
min_up_time=3,
p_nom=1000,
)
nu.add("Load", "load", bus="bus", p_set=[4000, 800, 5000, 3000])
[7]:
nu.lopf()
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
INFO:pypsa.linopf:Total preparation time: 0.05s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 2.96e+05
[7]:
('ok', 'optimal')
[8]:
nu.generators_t.status
[8]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 1.0 | 0.0 |
1 | 0.0 | 1.0 |
2 | 1.0 | 0.0 |
3 | 1.0 | 0.0 |
[9]:
nu.generators_t.p
[9]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 4000.0 | 0.0 |
1 | 0.0 | 800.0 |
2 | 5000.0 | 0.0 |
3 | 3000.0 | 0.0 |
Minimum down time demonstration#
Coal has a minimum down time, forcing it to go off longer.
[10]:
nu = pypsa.Network(snapshots=range(4))
nu.add("Bus", "bus")
nu.add(
"Generator",
"coal",
bus="bus",
committable=True,
p_min_pu=0.3,
marginal_cost=20,
min_down_time=2,
down_time_before=1,
p_nom=10000,
)
nu.add(
"Generator",
"gas",
bus="bus",
committable=True,
marginal_cost=70,
p_min_pu=0.1,
p_nom=4000,
)
nu.add("Load", "load", bus="bus", p_set=[3000, 800, 3000, 8000])
[11]:
nu.lopf()
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
INFO:pypsa.linopf:Total preparation time: 0.05s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 3.36e+05
[11]:
('ok', 'optimal')
[12]:
nu.objective
[12]:
336000.0
[13]:
nu.generators_t.status
[13]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 1.0 | 0.0 |
1 | 0.0 | 1.0 |
2 | 1.0 | 0.0 |
3 | 1.0 | 0.0 |
[14]:
nu.generators_t.p
[14]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 3000.0 | 0.0 |
1 | 0.0 | 800.0 |
2 | 3000.0 | 0.0 |
3 | 8000.0 | 0.0 |
Start up and shut down costs#
Now there are associated costs for shutting down, etc
[15]:
nu = pypsa.Network(snapshots=range(4))
nu.add("Bus", "bus")
nu.add(
"Generator",
"coal",
bus="bus",
committable=True,
p_min_pu=0.3,
marginal_cost=20,
min_down_time=2,
start_up_cost=5000,
p_nom=10000,
)
nu.add(
"Generator",
"gas",
bus="bus",
committable=True,
marginal_cost=70,
p_min_pu=0.1,
shut_down_cost=25,
p_nom=4000,
)
nu.add("Load", "load", bus="bus", p_set=[3000, 800, 3000, 8000])
[16]:
nu.lopf(nu.snapshots)
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
INFO:pypsa.linopf:Total preparation time: 0.05s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 3.36e+05
[16]:
('ok', 'optimal')
[17]:
nu.objective
[17]:
336000.0
[18]:
nu.generators_t.status
[18]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 1.0 | 0.0 |
1 | 0.0 | 1.0 |
2 | 1.0 | 0.0 |
3 | 1.0 | 0.0 |
[19]:
nu.generators_t.p
[19]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 3000.0 | 0.0 |
1 | 0.0 | 800.0 |
2 | 3000.0 | 0.0 |
3 | 8000.0 | 0.0 |
Ramp rate limits#
[20]:
nu = pypsa.Network(snapshots=range(6))
nu.add("Bus", "bus")
nu.add(
"Generator",
"coal",
bus="bus",
marginal_cost=20,
ramp_limit_up=0.1,
ramp_limit_down=0.2,
p_nom=10000,
)
nu.add("Generator", "gas", bus="bus", marginal_cost=70, p_nom=4000)
nu.add("Load", "load", bus="bus", p_set=[4000, 7000, 7000, 7000, 7000, 3000])
[21]:
nu.lopf()
INFO:pypsa.linopf:Prepare linear problem
INFO:pypsa.linopf:Total preparation time: 0.06s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
INFO:pypsa.linopf:Optimization successful. Objective value: 9.50e+05
[21]:
('ok', 'optimal')
[22]:
nu.generators_t.p
[22]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 4000.0 | 0.0 |
1 | 5000.0 | 2000.0 |
2 | 6000.0 | 1000.0 |
3 | 7000.0 | 0.0 |
4 | 5000.0 | 2000.0 |
5 | 3000.0 | 0.0 |
[23]:
nu = pypsa.Network(snapshots=range(6))
nu.add("Bus", "bus")
nu.add(
"Generator",
"coal",
bus="bus",
marginal_cost=20,
ramp_limit_up=0.1,
ramp_limit_down=0.2,
p_nom_extendable=True,
capital_cost=1e2,
)
nu.add("Generator", "gas", bus="bus", marginal_cost=70, p_nom=4000)
nu.add("Load", "load", bus="bus", p_set=[4000, 7000, 7000, 7000, 7000, 3000])
[24]:
nu.lopf(nu.snapshots)
INFO:pypsa.linopf:Prepare linear problem
INFO:pypsa.linopf:Total preparation time: 0.07s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
INFO:pypsa.linopf:Optimization successful. Objective value: 1.68e+06
[24]:
('ok', 'optimal')
[25]:
nu.generators.p_nom_opt
[25]:
Generator
coal 5000.0
gas 4000.0
Name: p_nom_opt, dtype: float64
[26]:
nu.generators_t.p
[26]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 4000.0 | 0.0 |
1 | 4500.0 | 2500.0 |
2 | 5000.0 | 2000.0 |
3 | 5000.0 | 2000.0 |
4 | 4000.0 | 3000.0 |
5 | 3000.0 | 0.0 |
[27]:
nu = pypsa.Network(snapshots=range(7))
nu.add("Bus", "bus")
# Can get bad interactions if SU > RU and p_min_pu; similarly if SD > RD
nu.add(
"Generator",
"coal",
bus="bus",
marginal_cost=20,
committable=True,
p_min_pu=0.05,
initial_status=0,
ramp_limit_start_up=0.1,
ramp_limit_up=0.2,
ramp_limit_down=0.25,
ramp_limit_shut_down=0.15,
p_nom=10000.0,
)
nu.add("Generator", "gas", bus="bus", marginal_cost=70, p_nom=10000)
nu.add("Load", "load", bus="bus", p_set=[0.0, 200.0, 7000, 7000, 7000, 2000, 0])
WARNING:pypsa.components:Generator has no attribute initial_status, ignoring this passed value.
[28]:
nu.lopf()
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
INFO:pypsa.linopf:Total preparation time: 0.09s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 1.15e+06
[28]:
('ok', 'optimal')
[29]:
nu.generators_t.p
[29]:
Generator | coal | gas |
---|---|---|
snapshot | ||
0 | 0.0 | 0.0 |
1 | 0.0 | 200.0 |
2 | 1000.0 | 6000.0 |
3 | 3000.0 | 4000.0 |
4 | 4000.0 | 3000.0 |
5 | 1500.0 | 500.0 |
6 | 0.0 | 0.0 |
[30]:
nu.generators_t.status
[30]:
Generator | coal |
---|---|
snapshot | |
0 | 0.0 |
1 | 0.0 |
2 | 1.0 |
3 | 1.0 |
4 | 1.0 |
5 | 1.0 |
6 | 0.0 |
[31]:
nu.generators.loc["coal"]
[31]:
attribute
bus bus
control Slack
type
p_nom 10000.0
p_nom_extendable False
p_nom_min 0.0
p_nom_max inf
p_min_pu 0.05
p_max_pu 1.0
p_set 0.0
q_set 0.0
sign 1.0
carrier
marginal_cost 20.0
build_year 0
lifetime inf
capital_cost 0.0
efficiency 1.0
committable True
start_up_cost 0.0
shut_down_cost 0.0
min_up_time 0
min_down_time 0
up_time_before 1
down_time_before 0
ramp_limit_up 0.2
ramp_limit_down 0.25
ramp_limit_start_up 0.1
ramp_limit_shut_down 0.15
p_nom_opt 10000.0
Name: coal, dtype: object
Rolling horizon example#
This example solves sequentially in batches
[32]:
sets_of_snapshots = 6
p_set = [4000, 5000, 700, 800, 4000]
nu = pypsa.Network(snapshots=range(len(p_set) * sets_of_snapshots))
nu.add("Bus", "bus")
nu.add(
"Generator",
"coal",
bus="bus",
committable=True,
p_min_pu=0.3,
marginal_cost=20,
min_down_time=2,
min_up_time=3,
up_time_before=1,
ramp_limit_up=1,
ramp_limit_down=1,
ramp_limit_start_up=1,
ramp_limit_shut_down=1,
shut_down_cost=150,
start_up_cost=200,
p_nom=10000,
)
nu.add(
"Generator",
"gas",
bus="bus",
committable=True,
marginal_cost=70,
p_min_pu=0.1,
up_time_before=2,
min_up_time=3,
shut_down_cost=20,
start_up_cost=50,
p_nom=1000,
)
nu.add("Load", "load", bus="bus", p_set=p_set * sets_of_snapshots)
[33]:
overlap = 2
for i in range(sets_of_snapshots):
nu.lopf(nu.snapshots[i * len(p_set) : (i + 1) * len(p_set) + overlap], pyomo=False)
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
INFO:pypsa.linopf:Total preparation time: 0.08s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 5.45e+05
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/linopf.py:337: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
rhs.loc[sns[0]] += (limit_up - limit_start) * status_prev_fix
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/linopf.py:379: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
rhs.loc[sns[0]] += -limit_shut * status_prev_fix
INFO:pypsa.linopf:Total preparation time: 0.09s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 5.45e+05
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/linopf.py:337: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
rhs.loc[sns[0]] += (limit_up - limit_start) * status_prev_fix
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/linopf.py:379: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
rhs.loc[sns[0]] += -limit_shut * status_prev_fix
INFO:pypsa.linopf:Total preparation time: 0.09s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 5.45e+05
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/linopf.py:337: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
rhs.loc[sns[0]] += (limit_up - limit_start) * status_prev_fix
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/linopf.py:379: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
rhs.loc[sns[0]] += -limit_shut * status_prev_fix
INFO:pypsa.linopf:Total preparation time: 0.09s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 5.45e+05
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/linopf.py:337: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
rhs.loc[sns[0]] += (limit_up - limit_start) * status_prev_fix
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/linopf.py:379: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
rhs.loc[sns[0]] += -limit_shut * status_prev_fix
INFO:pypsa.linopf:Total preparation time: 0.09s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 5.45e+05
WARNING:pypsa.linopf:Unit commitment is not yet completely implemented for optimising without pyomo. Thus minimum up time, minimum down time, start up costs, shut down costs will be ignored.
INFO:pypsa.linopf:Prepare linear problem
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/linopf.py:337: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
rhs.loc[sns[0]] += (limit_up - limit_start) * status_prev_fix
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/lib/python3.11/site-packages/pypsa/linopf.py:379: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
rhs.loc[sns[0]] += -limit_shut * status_prev_fix
INFO:pypsa.linopf:Total preparation time: 0.09s
INFO:pypsa.linopf:Solve linear problem using Glpk solver
WARNING:pypsa.linopt:Shadow prices of MILP couldn't be parsed
INFO:pypsa.linopf:Optimization successful. Objective value: 3.65e+05
[34]:
pd.concat(
{"Active": nu.generators_t.status.astype(bool), "Output": nu.generators_t.p}, axis=1
)
[34]:
Active | Output | |||
---|---|---|---|---|
Generator | coal | gas | coal | gas |
snapshot | ||||
0 | True | False | 4000.0 | 0.0 |
1 | True | False | 5000.0 | 0.0 |
2 | False | True | 0.0 | 700.0 |
3 | False | True | 0.0 | 800.0 |
4 | True | False | 4000.0 | 0.0 |
5 | True | False | 4000.0 | 0.0 |
6 | True | False | 5000.0 | 0.0 |
7 | False | True | 0.0 | 700.0 |
8 | False | True | 0.0 | 800.0 |
9 | True | False | 4000.0 | 0.0 |
10 | True | False | 4000.0 | 0.0 |
11 | True | False | 5000.0 | 0.0 |
12 | False | True | 0.0 | 700.0 |
13 | False | True | 0.0 | 800.0 |
14 | True | False | 4000.0 | 0.0 |
15 | True | False | 4000.0 | 0.0 |
16 | True | False | 5000.0 | 0.0 |
17 | False | True | 0.0 | 700.0 |
18 | False | True | 0.0 | 800.0 |
19 | True | False | 4000.0 | 0.0 |
20 | True | False | 4000.0 | 0.0 |
21 | True | False | 5000.0 | 0.0 |
22 | False | True | 0.0 | 700.0 |
23 | False | True | 0.0 | 800.0 |
24 | True | False | 4000.0 | 0.0 |
25 | True | False | 4000.0 | 0.0 |
26 | True | False | 5000.0 | 0.0 |
27 | False | True | 0.0 | 700.0 |
28 | False | True | 0.0 | 800.0 |
29 | True | False | 4000.0 | 0.0 |