88
99import pypsa
1010
11- from pypsa .linopt import get_var , linexpr , define_constraints
12-
1311from 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+
110109def 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
130138def 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+
207198def 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