Skip to content

Commit 950a835

Browse files
committed
solving conflicts and implement logic in elements [skip ci]
2 parents 9261239 + 8704750 commit 950a835

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1712
-405
lines changed

.github/workflows/main.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,16 @@ jobs:
195195

196196
- name: Check package can be imported
197197
run: |
198+
mkdir tmp_for_import_checking
199+
cd tmp_for_import_checking
198200
python -c "import lightsim2grid"
199201
python -c "from lightsim2grid import *"
200202
python -c "from lightsim2grid.newtonpf import newtonpf"
203+
cd ..
201204
202205
- name: Check LightSimBackend can be imported
203206
run: |
207+
cd tmp_for_import_checking
204208
python -m pip install grid2op
205209
python -c "from lightsim2grid import LightSimBackend"
206210
python -c "from lightsim2grid import LightSimBackend; import grid2op; env = grid2op.make('l2rpn_case14_sandbox', test=True, backend=LightSimBackend())"
@@ -253,16 +257,20 @@ jobs:
253257

254258
- name: Check package can be imported
255259
run: |
260+
mkdir tmp_for_import_checking
261+
cd tmp_for_import_checking
256262
python -c "import lightsim2grid"
257263
python -c "from lightsim2grid import *"
258264
python -c "from lightsim2grid.newtonpf import newtonpf"
265+
cd ..
259266
260267
- name: Check LightSimBackend can be imported
261268
run: |
269+
cd tmp_for_import_checking
262270
python -m pip install grid2op
263271
python -c "from lightsim2grid import LightSimBackend"
264272
python -c "from lightsim2grid import LightSimBackend; import grid2op; env = grid2op.make('l2rpn_case14_sandbox', test=True, backend=LightSimBackend())"
265-
273+
266274
- name: Upload wheel
267275
uses: actions/upload-artifact@v3
268276
with:

CHANGELOG.rst

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Change Log
55
--------
66
- [refacto] have a structure in cpp for the buses
77
- [refacto] have the id_grid_to_solver and id_solver_to_grid etc. directly in the solver and NOT in the gridmodel.
8+
- [refacto] put some method in the DataGeneric as well as some attribute (_status for example)
89
- support 3w trafo (as modeled in pandapower)
910
- improve speed by not performing internal checks
1011
(keep check for boundaries and all for python API instead) [see `TODO DEBUG MODE` in c++ code]
@@ -13,8 +14,6 @@ Change Log
1314
- a mode to do both `Computer` and `SecurityAnalysisCPP`
1415
- use the "multi slack hack" (see issue #50) for SecurityAnalysis or Computer for example
1516
- code `helm` powerflow method
16-
- possibility to read CGMES files
17-
- possibility to read XIIDM files
1817
- interface with gridpack (to enforce q limits for example)
1918
- maybe have a look at suitesparse "sliplu" tools ?
2019
- easier building (get rid of the "make" part)
@@ -25,7 +24,15 @@ Change Log
2524
- [FIXED] now voltage is properly set to 0. when storage units are disconnected
2625
- [FIXED] a bug where non connected grid were not spotted in DC
2726
- [FIXED] a bug when trying to set the slack for a non existing genererator
28-
- [IMPROVED] now making the new grid2op `create_test_suite`
27+
- [FIXED] a bug in init from pypowsybl when some object were disconnected. It raises
28+
an error (because they are not connected to a bus): now this function properly handles
29+
these cases.
30+
- [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this
31+
one.
32+
- [ADDED] possibility to set / retrieve the names of each elements of the grid.
33+
- [ADDED] embed in the generator models the "non pv" behaviour. (TODO need to be able to change Q from python side)
34+
- [IMPROVED] now performing the new grid2op `create_test_suite`
35+
- [IMPROVED] now lightsim2grid properly throw `BackendError`
2936

3037
[0.7.5] 2023-10-05
3138
--------------------

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
author = 'Benjamin DONNOT'
2323

2424
# The full version, including alpha/beta/rc tags
25-
release = "0.7.6.dev0"
25+
release = "0.7.6.dev1"
2626
version = '0.7'
2727

2828
# -- General configuration ---------------------------------------------------

lightsim2grid/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# you can obtain one at http://mozilla.org/MPL/2.0/.
66
# SPDX-License-Identifier: MPL-2.0
77
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
8-
__version__ = "0.7.6.dev0"
8+
__version__ = "0.7.6.dev1"
99

1010
__all__ = ["newtonpf", "SolverType", "ErrorType", "solver"]
1111

lightsim2grid/gridmodel/from_pypowsybl.py

Lines changed: 109 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,43 @@
66
# SPDX-License-Identifier: MPL-2.0
77
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
88

9-
import copy
109
import numpy as np
1110
import pypowsybl as pypo
12-
# import pypowsybl.loadflow as lf
1311

1412
from lightsim2grid_cpp import GridModel
1513

1614

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+
1736
def init(net : pypo.network,
1837
gen_slack_id: int = None,
1938
slack_bus_id: int = None,
2039
sn_mva = 100.,
2140
sort_index=True,
22-
f_hz = 50.):
41+
f_hz = 50., # unused
42+
only_main_component=True):
2343
model = GridModel()
44+
# model.set_f_hz(f_hz)
45+
2446
# for substation
2547
# network.get_voltage_levels()["substation_id"]
2648
# network.get_substations()
@@ -50,42 +72,39 @@ def init(net : pypo.network,
5072
df_gen = net.get_generators().sort_index()
5173
else:
5274
df_gen = net.get_generators()
53-
5475
# to handle encoding in 32 bits and overflow when "splitting" the Q values among
5576
min_q = df_gen["min_q"].values.astype(np.float32)
5677
max_q = df_gen["max_q"].values.astype(np.float32)
5778
min_q[~np.isfinite(min_q)] = np.finfo(np.float32).min * 0.5 + 1.
5879
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+
8094
# for loads
8195
if sort_index:
8296
df_load = net.get_loads().sort_index()
8397
else:
8498
df_load = net.get_loads()
99+
load_bus, load_disco = _aux_get_bus(bus_df, df_load)
85100
model.init_loads(df_load["p0"].values,
86101
df_load["q0"].values,
87-
1 * bus_df.loc[df_load["bus_id"].values]["bus_id"].values
102+
load_bus
88103
)
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)
89108

90109
# for lines
91110
if sort_index:
@@ -117,13 +136,19 @@ def init(net : pypo.network,
117136
g2 = df_line["g2"].values * v2*v2/sn_mva + (v2-v1)*tmp_.real*v2/sn_mva
118137
line_h_or = (b1 + 1j * g1)
119138
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")
120141
model.init_powerlines_full(line_r,
121142
line_x,
122143
line_h_or,
123144
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
126147
)
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)
127152

128153
# for trafo
129154
if sort_index:
@@ -145,51 +170,50 @@ def init(net : pypo.network,
145170
has_tap = tap_step_pct != 0.
146171
tap_pos[has_tap] += 1
147172
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")
148175
model.init_trafo(df_trafo["r"].values / trafo_to_pu,
149176
df_trafo["x"].values / trafo_to_pu,
150177
2.*(1j*df_trafo["g"].values + df_trafo["b"].values) * trafo_to_pu,
151178
tap_step_pct,
152179
tap_pos,
153180
shift_,
154181
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)
157188

158189
# for shunt
159190
if sort_index:
160191
df_shunt = net.get_shunt_compensators().sort_index()
161192
else:
162193
df_shunt = net.get_shunt_compensators()
163194

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)
175196
shunt_kv = voltage_levels.loc[df_shunt["voltage_level_id"].values]["nominal_v"].values
176197
model.init_shunt(-df_shunt["g"].values * shunt_kv**2,
177198
-df_shunt["b"].values * shunt_kv**2,
178-
1 * bus_df.loc[df_shunt["bus_id"].values]["bus_id"].values
199+
sh_bus
179200
)
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:
182203
model.deactivate_shunt(shunt_id)
204+
model.set_shunt_names(df_trafo.index)
183205

184206
# for hvdc (TODO not tested yet)
185207
df_dc = net.get_hvdc_lines().sort_index()
186208
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])
189213
loss_percent = np.zeros(df_dc.shape[0]) # TODO
190214
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,
193217
df_dc["target_p"].values,
194218
loss_percent,
195219
loss_mw,
@@ -200,22 +224,56 @@ def init(net : pypo.network,
200224
df_sations.loc[df_dc["converter_station2_id"].values]["min_q"].values,
201225
df_sations.loc[df_dc["converter_station2_id"].values]["max_q"].values
202226
)
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+
204233
# storage units (TODO not tested yet)
205234
if sort_index:
206235
df_batt = net.get_batteries().sort_index()
207236
else:
208237
df_batt = net.get_batteries()
238+
batt_bus, batt_disco = _aux_get_bus(bus_df, df_batt)
209239
model.init_storages(df_batt["target_p"].values,
210240
df_batt["target_q"].values,
211-
1 * bus_df.loc[df_batt["bus_id"].values]["bus_id"].values
241+
batt_bus
212242
)
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)
213266

214267
# TODO
215268
# sgen => regular gen (from net.get_generators()) with voltage_regulator off TODO
216269

217270
# TODO checks
218271
# no 3windings trafo and other exotic stuff
219272
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()
221279
return model

0 commit comments

Comments
 (0)