@@ -102,60 +102,104 @@ def add_power_limits(n, investment_year, limits_power_max):
102102 """
103103 " Restricts the maximum inflow/outflow of electricity from/to a country.
104104 """
105+
106+ def add_pos_neg_aux_variables (n , idx , infix ):
107+ """
108+ For every snapshot in the network `n` this functions adds auxiliary variables corresponding to the positive and negative parts of the dynamical variables of the network components specified in the index `idx`. The `infix` parameter is used to create unique names for the auxiliary variables and constraints.
109+
110+ Parameters
111+ ----------
112+ n : pypsa.Network
113+ The PyPSA network object containing the model.
114+ idx : pandas.Index
115+ The index of the network component (e.g., lines or links) for which to create auxiliary variables.
116+ infix : str
117+ A string used to create unique names for the auxiliary variables and constraints.
118+ """
119+
120+ var_key = f"{ idx .name } -{ 's' if idx .name == 'Line' else 'p' } "
121+ var = n .model [var_key ].sel ({idx .name : idx })
122+ aux_pos = n .model .add_variables (
123+ name = f"{ var_key } -{ infix } -aux-pos" ,
124+ lower = 0 ,
125+ coords = [n .snapshots , idx ],
126+ )
127+ aux_neg = n .model .add_variables (
128+ name = f"{ var_key } -{ infix } -aux-neg" ,
129+ upper = 0 ,
130+ coords = [n .snapshots , idx ],
131+ )
132+ n .model .add_constraints (
133+ aux_pos >= var ,
134+ name = f"{ var_key } -{ infix } -aux-pos-constr" ,
135+ )
136+ n .model .add_constraints (
137+ aux_neg <= var ,
138+ name = f"{ var_key } -{ infix } -aux-neg-constr" ,
139+ )
140+ return aux_pos , aux_neg
141+
105142 for ct in limits_power_max :
106143 if investment_year not in limits_power_max [ct ].keys ():
107144 continue
108145
109- limit = 1e3 * limits_power_max [ct ][investment_year ] / 10
146+ lim = 1e3 * limits_power_max [ct ][investment_year ] # in MW
110147
111148 logger .info (
112- f"Adding constraint on electricity import/export from/to { ct } to be < { limit } MW"
149+ f"Adding constraint on electricity import/export from/to { ct } to be < { lim } MW"
113150 )
114- incoming_line = n .lines .index [
115- (n .lines .carrier == "AC" )
116- & (n .lines .bus0 .str [:2 ] != ct )
117- & (n .lines .bus1 .str [:2 ] == ct )
118- ]
119- outgoing_line = n .lines .index [
120- (n .lines .carrier == "AC" )
121- & (n .lines .bus0 .str [:2 ] == ct )
122- & (n .lines .bus1 .str [:2 ] != ct )
123- ]
151+ # identify interconnectors
124152
125- incoming_link = n .links .index [
126- (n .links .carrier == "DC" )
127- & (n .links .bus0 .str [:2 ] != ct )
128- & (n .links .bus1 .str [:2 ] == ct )
129- ]
130- outgoing_link = n .links .index [
131- (n .links .carrier == "DC" )
132- & (n .links .bus0 .str [:2 ] == ct )
133- & (n .links .bus1 .str [:2 ] != ct )
134- ]
153+ incoming_lines = n .lines .query (
154+ f"not bus0.str.startswith('{ ct } ') and bus1.str.startswith('{ ct } ') and active"
155+ )
156+ outgoing_lines = n .lines .query (
157+ f"bus0.str.startswith('{ ct } ') and not bus1.str.startswith('{ ct } ') and active"
158+ )
159+ incoming_links = n .links .query (
160+ f"not bus0.str.startswith('{ ct } ') and bus1.str.startswith('{ ct } ') and carrier == 'DC' and active"
161+ )
162+ outgoing_links = n .links .query (
163+ f"bus0.str.startswith('{ ct } ') and not bus1.str.startswith('{ ct } ') and carrier == 'DC' and active"
164+ )
165+
166+ # define auxiliary variables for positive and negative parts of line and link flows
167+
168+ incoming_lines_aux_pos , incoming_lines_aux_neg = add_pos_neg_aux_variables (
169+ n , incoming_lines .index , f"incoming-{ ct } "
170+ )
171+
172+ outgoing_lines_aux_pos , outgoing_lines_aux_neg = add_pos_neg_aux_variables (
173+ n , outgoing_lines .index , f"outgoing-{ ct } "
174+ )
175+
176+ incoming_links_aux_pos , incoming_links_aux_neg = add_pos_neg_aux_variables (
177+ n , incoming_links .index , f"incoming-{ ct } "
178+ )
179+
180+ outgoing_links_aux_pos , outgoing_links_aux_neg = add_pos_neg_aux_variables (
181+ n , outgoing_links .index , f"outgoing-{ ct } "
182+ )
135183
136- # iterate over snapshots - otherwise exporting of postnetwork fails since
137- # the constraints are time dependent
138- for t in n .snapshots :
139- incoming_line_p = n .model ["Line-s" ].loc [t , incoming_line ]
140- outgoing_line_p = n .model ["Line-s" ].loc [t , outgoing_line ]
141- incoming_link_p = n .model ["Link-p" ].loc [t , incoming_link ]
142- outgoing_link_p = n .model ["Link-p" ].loc [t , outgoing_link ]
143-
144- lhs = (
145- incoming_link_p .sum ()
146- - outgoing_link_p .sum ()
147- + incoming_line_p .sum ()
148- - outgoing_line_p .sum ()
149- ) / 10
150- # divide by 10 to avoid numerical issues
151-
152- cname_upper = f"Power-import-limit-{ ct } -{ t } "
153- cname_lower = f"Power-export-limit-{ ct } -{ t } "
154-
155- n .model .add_constraints (lhs <= limit , name = cname_upper )
156- n .model .add_constraints (lhs >= - limit , name = cname_lower )
157-
158- # not adding to network as the shadow prices are not needed
184+ # To constraint the absolute values of imports and exports, we have to sum the
185+ # corresponding positive and negative flows separately, using the auxiliary variables
186+
187+ import_lhs = (
188+ incoming_links_aux_pos .sum (dim = "Link" )
189+ + incoming_lines_aux_pos .sum (dim = "Line" )
190+ - outgoing_links_aux_neg .sum (dim = "Link" )
191+ - outgoing_lines_aux_neg .sum (dim = "Line" )
192+ ) / 10
193+
194+ export_lhs = (
195+ outgoing_links_aux_pos .sum (dim = "Link" )
196+ + outgoing_lines_aux_pos .sum (dim = "Line" )
197+ - incoming_links_aux_neg .sum (dim = "Link" )
198+ - incoming_lines_aux_neg .sum (dim = "Line" )
199+ ) / 10
200+
201+ n .model .add_constraints (import_lhs <= lim / 10 , name = f"Power-import-limit-{ ct } " )
202+ n .model .add_constraints (export_lhs <= lim / 10 , name = f"Power-export-limit-{ ct } " )
159203
160204
161205def h2_import_limits (n , investment_year , limits_volume_max ):
0 commit comments