6
6
# SPDX-License-Identifier: MPL-2.0
7
7
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
8
8
9
- import copy
10
9
import numpy as np
11
10
import pypowsybl as pypo
12
- # import pypowsybl.loadflow as lf
13
11
14
12
from lightsim2grid_cpp import GridModel
15
13
16
14
15
+ def _aux_get_bus (bus_df , df , conn_key = "connected" , bus_key = "bus_id" ):
16
+ if df .shape [0 ] == 0 :
17
+ # no element of this type so no problem
18
+ return np .zeros (0 , dtype = int ), np .ones (0 , dtype = bool )
19
+ # retrieve which elements are disconnected
20
+ mask_disco = ~ df [conn_key ]
21
+ if mask_disco .all ():
22
+ raise RuntimeError ("All element of the same type are disconnected, the init will not work." )
23
+ first_el_co = np .where (~ mask_disco .values )[0 ][0 ]
24
+ # retrieve the bus where the element are
25
+ tmp_bus_id = df [bus_key ].copy ()
26
+ tmp_bus_id [mask_disco ] = df .iloc [first_el_co ][bus_key ] # assign a "random" bus to disco element
27
+ bus_id = bus_df .loc [tmp_bus_id .values ]["bus_id" ].values
28
+ # deactivate the element not on the main component
29
+ # wrong_component = bus_df.loc[tmp_bus_id.values]["connected_component"].values != 0
30
+ # mask_disco[wrong_component] = True
31
+ # assign bus -1 to disconnected elements
32
+ bus_id [mask_disco ] = - 1
33
+ return bus_id , mask_disco .values
34
+
35
+
17
36
def init (net : pypo .network ,
18
37
gen_slack_id : int = None ,
19
38
slack_bus_id : int = None ,
20
39
sn_mva = 100. ,
21
40
sort_index = True ,
22
- f_hz = 50. ):
41
+ f_hz = 50. , # unused
42
+ only_main_component = True ):
23
43
model = GridModel ()
44
+ # model.set_f_hz(f_hz)
45
+
24
46
# for substation
25
47
# network.get_voltage_levels()["substation_id"]
26
48
# network.get_substations()
@@ -50,42 +72,39 @@ def init(net : pypo.network,
50
72
df_gen = net .get_generators ().sort_index ()
51
73
else :
52
74
df_gen = net .get_generators ()
53
-
54
75
# to handle encoding in 32 bits and overflow when "splitting" the Q values among
55
76
min_q = df_gen ["min_q" ].values .astype (np .float32 )
56
77
max_q = df_gen ["max_q" ].values .astype (np .float32 )
57
78
min_q [~ np .isfinite (min_q )] = np .finfo (np .float32 ).min * 0.5 + 1.
58
79
max_q [~ np .isfinite (max_q )] = np .finfo (np .float32 ).max * 0.5 - 1.
59
- model .init_generators (df_gen ["target_p" ].values ,
60
- df_gen ["target_v" ].values / voltage_levels .loc [df_gen ["voltage_level_id" ].values ]["nominal_v" ].values ,
61
- min_q ,
62
- max_q ,
63
- 1 * bus_df .loc [df_gen ["bus_id" ].values ]["bus_id" ].values
64
- )
65
- # TODO dist slack
66
- if gen_slack_id is not None :
67
- model .add_gen_slackbus (gen_slack_id , 1. )
68
- elif slack_bus_id is not None :
69
- gen_bus = np .array ([el .bus_id for el in model .get_generators ()])
70
- gen_is_conn_slack = gen_bus == model ._orig_to_ls [slack_bus_id ]
71
- nb_conn = gen_is_conn_slack .sum ()
72
- if nb_conn == 0 :
73
- raise RuntimeError (f"There is no generator connected to bus { slack_bus_id } . It cannot be the slack" )
74
- for gen_id , is_slack in enumerate (gen_is_conn_slack ):
75
- if is_slack :
76
- model .add_gen_slackbus (gen_id , 1. / nb_conn )
77
- else :
78
- model .add_gen_slackbus (0 , 1. )
79
-
80
+ gen_bus , gen_disco = _aux_get_bus (bus_df , df_gen )
81
+ model .init_generators_full (df_gen ["target_p" ].values ,
82
+ df_gen ["target_v" ].values / voltage_levels .loc [df_gen ["voltage_level_id" ].values ]["nominal_v" ].values ,
83
+ df_gen ["target_q" ].values ,
84
+ df_gen ["voltage_regulator_on" ].values ,
85
+ min_q ,
86
+ max_q ,
87
+ gen_bus
88
+ )
89
+ for gen_id , is_disco in enumerate (gen_disco ):
90
+ if is_disco :
91
+ model .deactivate_gen (gen_id )
92
+ model .set_gen_names (df_gen .index )
93
+
80
94
# for loads
81
95
if sort_index :
82
96
df_load = net .get_loads ().sort_index ()
83
97
else :
84
98
df_load = net .get_loads ()
99
+ load_bus , load_disco = _aux_get_bus (bus_df , df_load )
85
100
model .init_loads (df_load ["p0" ].values ,
86
101
df_load ["q0" ].values ,
87
- 1 * bus_df . loc [ df_load [ "bus_id" ]. values ][ "bus_id" ]. values
102
+ load_bus
88
103
)
104
+ for load_id , is_disco in enumerate (load_disco ):
105
+ if is_disco :
106
+ model .deactivate_load (load_id )
107
+ model .set_load_names (df_load .index )
89
108
90
109
# for lines
91
110
if sort_index :
@@ -117,13 +136,19 @@ def init(net : pypo.network,
117
136
g2 = df_line ["g2" ].values * v2 * v2 / sn_mva + (v2 - v1 )* tmp_ .real * v2 / sn_mva
118
137
line_h_or = (b1 + 1j * g1 )
119
138
line_h_ex = (b2 + 1j * g2 )
139
+ lor_bus , lor_disco = _aux_get_bus (bus_df , df_line , conn_key = "connected1" , bus_key = "bus1_id" )
140
+ lex_bus , lex_disco = _aux_get_bus (bus_df , df_line , conn_key = "connected2" , bus_key = "bus2_id" )
120
141
model .init_powerlines_full (line_r ,
121
142
line_x ,
122
143
line_h_or ,
123
144
line_h_ex ,
124
- 1 * bus_df . loc [ df_line [ "bus1_id" ]. values ][ "bus_id" ]. values ,
125
- 1 * bus_df . loc [ df_line [ "bus2_id" ]. values ][ "bus_id" ]. values
145
+ lor_bus ,
146
+ lex_bus
126
147
)
148
+ for line_id , (is_or_disc , is_ex_disc ) in enumerate (zip (lor_disco , lex_disco )):
149
+ if is_or_disc or is_ex_disc :
150
+ model .deactivate_powerline (line_id )
151
+ model .set_line_names (df_line .index )
127
152
128
153
# for trafo
129
154
if sort_index :
@@ -145,51 +170,50 @@ def init(net : pypo.network,
145
170
has_tap = tap_step_pct != 0.
146
171
tap_pos [has_tap ] += 1
147
172
tap_step_pct [~ has_tap ] = 1.0 # or any other values...
173
+ tor_bus , tor_disco = _aux_get_bus (bus_df , df_trafo , conn_key = "connected1" , bus_key = "bus1_id" )
174
+ tex_bus , tex_disco = _aux_get_bus (bus_df , df_trafo , conn_key = "connected2" , bus_key = "bus2_id" )
148
175
model .init_trafo (df_trafo ["r" ].values / trafo_to_pu ,
149
176
df_trafo ["x" ].values / trafo_to_pu ,
150
177
2. * (1j * df_trafo ["g" ].values + df_trafo ["b" ].values ) * trafo_to_pu ,
151
178
tap_step_pct ,
152
179
tap_pos ,
153
180
shift_ ,
154
181
is_tap_hv_side ,
155
- 1 * bus_df .loc [df_trafo ["bus1_id" ].values ]["bus_id" ].values , # TODO do I need to change hv / lv
156
- 1 * bus_df .loc [df_trafo ["bus2_id" ].values ]["bus_id" ].values )
182
+ tor_bus , # TODO do I need to change hv / lv
183
+ tex_bus )
184
+ for t_id , (is_or_disc , is_ex_disc ) in enumerate (zip (tor_disco , tex_disco )):
185
+ if is_or_disc or is_ex_disc :
186
+ model .deactivate_trafo (t_id )
187
+ model .set_trafo_names (df_trafo .index )
157
188
158
189
# for shunt
159
190
if sort_index :
160
191
df_shunt = net .get_shunt_compensators ().sort_index ()
161
192
else :
162
193
df_shunt = net .get_shunt_compensators ()
163
194
164
- is_on = copy .deepcopy (df_shunt ["connected" ])
165
- if (~ is_on ).any ():
166
- df_shunt ["connected" ] = True
167
- net .update_shunt_compensators (df_shunt [["connected" ]])
168
- if sort_index :
169
- df_shunt = net .get_shunt_compensators ().sort_index ()
170
- else :
171
- df_shunt = net .get_shunt_compensators ()
172
- df_shunt ["connected" ] = is_on
173
- net .update_shunt_compensators (df_shunt [["connected" ]])
174
-
195
+ sh_bus , sh_disco = _aux_get_bus (bus_df , df_shunt )
175
196
shunt_kv = voltage_levels .loc [df_shunt ["voltage_level_id" ].values ]["nominal_v" ].values
176
197
model .init_shunt (- df_shunt ["g" ].values * shunt_kv ** 2 ,
177
198
- df_shunt ["b" ].values * shunt_kv ** 2 ,
178
- 1 * bus_df . loc [ df_shunt [ "bus_id" ]. values ][ "bus_id" ]. values
199
+ sh_bus
179
200
)
180
- for shunt_id , conn in enumerate (is_on ):
181
- if not conn :
201
+ for shunt_id , disco in enumerate (sh_disco ):
202
+ if disco :
182
203
model .deactivate_shunt (shunt_id )
204
+ model .set_shunt_names (df_trafo .index )
183
205
184
206
# for hvdc (TODO not tested yet)
185
207
df_dc = net .get_hvdc_lines ().sort_index ()
186
208
df_sations = net .get_vsc_converter_stations ().sort_index ()
187
- bus_from_id = df_sations .loc [df_dc ["converter_station1_id" ].values ]["bus_id" ].values
188
- bus_to_id = df_sations .loc [df_dc ["converter_station2_id" ].values ]["bus_id" ].values
209
+ # bus_from_id = df_sations.loc[df_dc["converter_station1_id"].values]["bus_id"].values
210
+ # bus_to_id = df_sations.loc[df_dc["converter_station2_id"].values]["bus_id"].values
211
+ bus_from_id , hvdc_from_disco = _aux_get_bus (bus_df , df_sations .loc [df_dc ["converter_station1_id" ].values ])
212
+ bus_to_id , hvdc_to_disco = _aux_get_bus (bus_df , df_sations .loc [df_dc ["converter_station2_id" ].values ])
189
213
loss_percent = np .zeros (df_dc .shape [0 ]) # TODO
190
214
loss_mw = np .zeros (df_dc .shape [0 ]) # TODO
191
- model .init_dclines (bus_df . loc [ bus_from_id ][ "bus_id" ]. values ,
192
- bus_df . loc [ bus_to_id ][ "bus_id" ]. values ,
215
+ model .init_dclines (bus_from_id ,
216
+ bus_to_id ,
193
217
df_dc ["target_p" ].values ,
194
218
loss_percent ,
195
219
loss_mw ,
@@ -200,22 +224,56 @@ def init(net : pypo.network,
200
224
df_sations .loc [df_dc ["converter_station2_id" ].values ]["min_q" ].values ,
201
225
df_sations .loc [df_dc ["converter_station2_id" ].values ]["max_q" ].values
202
226
)
203
-
227
+ # TODO will probably not work !
228
+ for hvdc_id , (is_or_disc , is_ex_disc ) in enumerate (zip (hvdc_from_disco , hvdc_to_disco )):
229
+ if is_or_disc or is_ex_disc :
230
+ model .deactivate_hvdc (hvdc_id )
231
+ model .set_dcline_names (df_sations .index )
232
+
204
233
# storage units (TODO not tested yet)
205
234
if sort_index :
206
235
df_batt = net .get_batteries ().sort_index ()
207
236
else :
208
237
df_batt = net .get_batteries ()
238
+ batt_bus , batt_disco = _aux_get_bus (bus_df , df_batt )
209
239
model .init_storages (df_batt ["target_p" ].values ,
210
240
df_batt ["target_q" ].values ,
211
- 1 * bus_df . loc [ df_batt [ "bus_id" ]. values ][ "bus_id" ]. values
241
+ batt_bus
212
242
)
243
+ for batt_id , disco in enumerate (batt_disco ):
244
+ if disco :
245
+ model .deactivate_storage (batt_id )
246
+ model .set_storage_names (df_batt .index )
247
+
248
+ # TODO dist slack
249
+ if gen_slack_id is None and slack_bus_id is None :
250
+ # if nothing is given, by default I assign a slack bus to a bus where a lot of lines are connected
251
+ # quite central in the grid
252
+ bus_id , gen_id = model .assign_slack_to_most_connected ()
253
+ elif gen_slack_id is not None :
254
+ if slack_bus_id is not None :
255
+ raise RuntimeError (f"You provided both gen_slack_id and slack_bus_id which is not possible." )
256
+ model .add_gen_slackbus (gen_slack_id , 1. )
257
+ elif slack_bus_id is not None :
258
+ gen_bus = np .array ([el .bus_id for el in model .get_generators ()])
259
+ gen_is_conn_slack = gen_bus == model ._orig_to_ls [slack_bus_id ]
260
+ nb_conn = gen_is_conn_slack .sum ()
261
+ if nb_conn == 0 :
262
+ raise RuntimeError (f"There is no generator connected to bus { slack_bus_id } . It cannot be the slack" )
263
+ for gen_id , is_slack in enumerate (gen_is_conn_slack ):
264
+ if is_slack :
265
+ model .add_gen_slackbus (gen_id , 1. / nb_conn )
213
266
214
267
# TODO
215
268
# sgen => regular gen (from net.get_generators()) with voltage_regulator off TODO
216
269
217
270
# TODO checks
218
271
# no 3windings trafo and other exotic stuff
219
272
if net .get_phase_tap_changers ().shape [0 ] > 0 :
220
- raise RuntimeError ("Impossible currently to init a grid with tap changers at the moment." )
273
+ pass
274
+ # raise RuntimeError("Impossible currently to init a grid with tap changers at the moment.")
275
+
276
+ # and now deactivate all elements and nodes not in the main component
277
+ if only_main_component :
278
+ model .consider_only_main_component ()
221
279
return model
0 commit comments