Skip to content

Commit f950088

Browse files
committed
add linopy
1 parent 188d35e commit f950088

File tree

2 files changed

+63
-73
lines changed

2 files changed

+63
-73
lines changed

environment.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ channels:
88
- bioconda
99
dependencies:
1010
- pip
11-
- pypsa>=0.18
11+
- python<=3.10 # highspy not available with 3.11
12+
- pypsa>=0.21.3
1213
- snakemake-minimal
1314
- yaml
1415
- numpy
@@ -27,3 +28,4 @@ dependencies:
2728
- geopandas
2829
- pip:
2930
- vresutils==0.3.1
31+
- highspy

scripts/solve_network.py

Lines changed: 60 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88

99
import pypsa
1010

11-
from pypsa.linopt import get_var, linexpr, define_constraints
12-
1311
from pypsa.descriptors import free_output_series_dataframes
1412

1513
# Suppress logging of the slack bus choices
@@ -107,95 +105,87 @@ def bau_mincapacities_rule(model, carrier):
107105
ext_gens_i = n.generators.index[n.generators.carrier.isin(conv_techs) & n.generators.p_nom_extendable]
108106
n.model.safe_peakdemand = pypsa.opt.Constraint(expr=sum(n.model.generator_p_nom[gen] for gen in ext_gens_i) >= peakdemand - exist_conv_caps)
109107

108+
110109
def add_eps_storage_constraint(n):
111110
if not hasattr(n, 'epsilon'):
112111
n.epsilon = 1e-5
113112
fix_sus_i = n.storage_units.index[~ n.storage_units.p_nom_extendable]
114113
n.model.objective.expr += sum(n.epsilon * n.model.state_of_charge[su, n.snapshots[0]] for su in fix_sus_i)
115114

116-
def add_battery_constraints(n):
117115

118-
chargers = n.links.index[n.links.carrier.str.contains("battery charger") & n.links.p_nom_extendable]
119-
dischargers = chargers.str.replace("charger","discharger")
116+
def add_battery_constraints(n):
117+
"""
118+
Add constraint ensuring that charger = discharger, i.e.
119+
1 * charger_size - efficiency * discharger_size = 0
120+
"""
121+
if not n.links.p_nom_extendable.any():
122+
return
120123

121-
link_p_nom = get_var(n, "Link", "p_nom")
124+
discharger_bool = n.links.index.str.contains("battery discharger")
125+
charger_bool = n.links.index.str.contains("battery charger")
122126

123-
lhs = linexpr((1,link_p_nom[chargers]),
124-
(-n.links.loc[dischargers, "efficiency"].values,
125-
link_p_nom[dischargers].values))
127+
dischargers_ext = n.links[discharger_bool].query("p_nom_extendable").index
128+
chargers_ext = n.links[charger_bool].query("p_nom_extendable").index
126129

127-
define_constraints(n, lhs, "=", 0, 'Link', 'charger_ratio')
130+
eff = n.links.efficiency[dischargers_ext].values
131+
lhs = (
132+
n.model["Link-p_nom"].loc[chargers_ext]
133+
- n.model["Link-p_nom"].loc[dischargers_ext] * eff
134+
)
135+
n.model.add_constraints(lhs == 0, name="Link-charger_ratio")
128136

129137

130138
def add_chp_constraints(n):
131-
132139
electric_bool = (n.links.index.str.contains("urban central")
133140
& n.links.index.str.contains("CHP")
134141
& n.links.index.str.contains("electric"))
135142
heat_bool = (n.links.index.str.contains("urban central")
136143
& n.links.index.str.contains("CHP")
137144
& n.links.index.str.contains("heat"))
138-
139145
electric = n.links.index[electric_bool]
140146
heat = n.links.index[heat_bool]
141147
electric_ext = n.links.index[electric_bool & n.links.p_nom_extendable]
142148
heat_ext = n.links.index[heat_bool & n.links.p_nom_extendable]
143149
electric_fix = n.links.index[electric_bool & ~n.links.p_nom_extendable]
144150
heat_fix = n.links.index[heat_bool & ~n.links.p_nom_extendable]
145151

152+
p = n.model["Link-p"] # dimension: [time, link]
146153

154+
# output ratio between heat and electricity and top_iso_fuel_line for extendable
147155
if not electric_ext.empty:
156+
p_nom = n.model["Link-p_nom"]
148157

149-
link_p_nom = get_var(n, "Link", "p_nom")
150-
151-
#ratio of output heat to electricity set by p_nom_ratio
152-
lhs = linexpr((n.links.loc[electric_ext,"efficiency"]
153-
*n.links.loc[electric_ext,'p_nom_ratio'],
154-
link_p_nom[electric_ext]),
155-
(-n.links.loc[heat_ext,"efficiency"].values,
156-
link_p_nom[heat_ext].values))
157-
define_constraints(n, lhs, "=", 0, 'chplink', 'fix_p_nom_ratio')
158-
159-
160-
if not electric.empty:
161-
162-
link_p = get_var(n, "Link", "p")
163-
164-
#backpressure
165-
lhs = linexpr((n.links.loc[electric,'c_b'].values
166-
*n.links.loc[heat,"efficiency"],
167-
link_p[heat]),
168-
(-n.links.loc[electric,"efficiency"].values,
169-
link_p[electric].values))
170-
171-
define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure')
172-
173-
174-
if not electric_ext.empty:
175-
176-
link_p_nom = get_var(n, "Link", "p_nom")
177-
link_p = get_var(n, "Link", "p")
178-
179-
#top_iso_fuel_line for extendable
180-
lhs = linexpr((1,link_p[heat_ext]),
181-
(1,link_p[electric_ext].values),
182-
(-1,link_p_nom[electric_ext].values))
183-
184-
define_constraints(n, lhs, "<=", 0, 'chplink', 'top_iso_fuel_line_ext')
158+
lhs = (
159+
p_nom.loc[electric_ext]
160+
* (n.links.p_nom_ratio * n.links.efficiency)[electric_ext].values
161+
- p_nom.loc[heat_ext] * n.links.efficiency[heat_ext].values
162+
)
163+
n.model.add_constraints(lhs == 0, name="chplink-fix_p_nom_ratio")
185164

165+
rename = {"Link-ext": "Link"}
166+
lhs = (
167+
p.loc[:, electric_ext]
168+
+ p.loc[:, heat_ext]
169+
- p_nom.rename(rename).loc[electric_ext]
170+
)
171+
n.model.add_constraints(lhs <= 0, name="chplink-top_iso_fuel_line_ext")
186172

173+
# top_iso_fuel_line for fixed
187174
if not electric_fix.empty:
175+
lhs = p.loc[:, electric_fix] + p.loc[:, heat_fix]
176+
rhs = n.links.p_nom[electric_fix]
177+
n.model.add_constraints(lhs <= rhs, name="chplink-top_iso_fuel_line_fix")
188178

189-
link_p = get_var(n, "Link", "p")
190-
191-
#top_iso_fuel_line for fixed
192-
lhs = linexpr((1,link_p[heat_fix]),
193-
(1,link_p[electric_fix].values))
194-
195-
define_constraints(n, lhs, "<=", n.links.loc[electric_fix,"p_nom"].values, 'chplink', 'top_iso_fuel_line_fix')
179+
# back-pressure
180+
if not electric.empty:
181+
lhs = (
182+
p.loc[:, heat] * (n.links.efficiency[heat] * n.links.c_b[electric].values)
183+
- p.loc[:, electric] * n.links.efficiency[electric]
184+
)
185+
n.model.add_constraints(lhs <= rhs, name="chplink-backpressure")
196186

197-
def add_land_use_constraint(n):
198187

188+
def add_land_use_constraint(n):
199189
#warning: this will miss existing offwind which is not classed AC-DC and has carrier 'offwind'
200190
for carrier in ['solar', 'onwind', 'offwind-ac', 'offwind-dc']:
201191
existing_capacities = n.generators.loc[n.generators.carrier==carrier,"p_nom"].groupby(n.generators.bus.map(n.buses.location)).sum()
@@ -204,6 +194,7 @@ def add_land_use_constraint(n):
204194

205195
n.generators.p_nom_max[n.generators.p_nom_max<0]=0.
206196

197+
207198
def extra_functionality(n, snapshots):
208199
#add_opts_constraints(n, opts)
209200
#add_eps_storage_constraint(n)
@@ -229,7 +220,7 @@ def solve_network(n, config=None, solver_log=None, opts=None):
229220
solver_log = snakemake.log.solver
230221
solver_name = solver_options.pop('name')
231222

232-
def run_lopf(n, allow_warning_status=False, fix_zero_lines=False, fix_ext_lines=False):
223+
def run_lopf(n, allow_warning_status=False, fix_zero_lines=False, fix_ext_lines=False, **kwargs):
233224
free_output_series_dataframes(n)
234225

235226
if fix_zero_lines:
@@ -267,22 +258,19 @@ def run_lopf(n, allow_warning_status=False, fix_zero_lines=False, fix_ext_lines=
267258
#print("Model is saved to MPS")
268259
#sys.exit()
269260

261+
status, termination_condition = n.optimize(
262+
solver_name=solver_name,
263+
extra_functionality=extra_functionality,
264+
**solver_options,
265+
**kwargs,
266+
)
270267

271-
status, termination_condition = n.lopf(pyomo=False,
272-
solver_name=solver_name,
273-
solver_logfile=solver_log,
274-
solver_options=solver_options,
275-
solver_dir=tmpdir,
276-
extra_functionality=extra_functionality,
277-
formulation=solve_opts['formulation'])
278-
#extra_postprocessing=extra_postprocessing
279-
#keep_files=True
280-
#free_memory={'pypsa'}
281-
282-
assert status == "ok" or allow_warning_status and status == 'warning', \
283-
("network_lopf did abort with status={} "
284-
"and termination_condition={}"
285-
.format(status, termination_condition))
268+
if status != "ok" or allow_warning_status and status == 'warning':
269+
logger.warning(
270+
f"Solving status '{status}' with termination condition '{termination_condition}'"
271+
)
272+
if "infeasible" in termination_condition:
273+
raise RuntimeError("Solving status 'infeasible'")
286274

287275
if not fix_ext_lines and "line_volume_constraint" in n.global_constraints.index:
288276
n.line_volume_limit_dual = n.global_constraints.at["line_volume_constraint","mu"]

0 commit comments

Comments
 (0)