Skip to content

Commit

Permalink
Fix bugs in JSON serialization (#291)
Browse files Browse the repository at this point in the history
Found and fixed the following bugs:
- Fix version checks in `network_from_dict`
- Emit proper warning for older versions
- Add missing `max_loading` for lines
- Add missing `max_loading` for transformers
- Add missing `tap` for transformers
- Fix error due to sorting of line parameters and transformer parameters
  with heterogeneous ID types
- Fix conversion of `single` and `center` transformer types to proper
  vector groups
- Fix upgrading of `max_current`, `conductor_type`, and `insulator_type`
  of line parameters

These bugs were found with testing more comprehensive JSON files.
Added files for all JSON versions and the corresponding file generator
scripts.

Also added a test to ensure these files do not get modified by mistake
when refactoring the code base.
  • Loading branch information
alihamdan authored Dec 1, 2024
1 parent b2be2b8 commit 7c062c0
Show file tree
Hide file tree
Showing 11 changed files with 3,086 additions and 1,473 deletions.
1 change: 1 addition & 0 deletions doc/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ og:description: See what's new in the latest release of Roseau Load Flow !

## Unreleased

- {gh-pr}`291` Fixed several bugs in JSON serialization and deserialization.
- {gh-pr}`289` Improve the `TransformerParameters` class and the transformers catalogue

- Add 15kV transformers to the catalogue (SE and FT)
Expand Down
61 changes: 45 additions & 16 deletions roseau/load_flow/io/dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,18 @@ def network_from_dict( # noqa: C901

version = data.get("version", 0)
if version <= 2:
logger.warning(
warnings.warn(
f"Got an outdated network file (version {version}), trying to update to the current format "
f"(version {NETWORK_JSON_VERSION}). Please save the network again."
f"(version {NETWORK_JSON_VERSION}). Please save the network again.",
category=UserWarning,
stacklevel=find_stack_level(),
)
if version == 0:
if version <= 0:
data = v0_to_v1_converter(data)
include_results = False # V0 network dictionaries didn't have results
if version == 1:
if version <= 1:
data = v1_to_v2_converter(data)
if version == 2:
if version <= 2:
data = v2_to_v3_converter(data)
else:
# If we arrive here, we dealt with all legacy versions, it must be the current one
Expand Down Expand Up @@ -177,11 +179,20 @@ def network_from_dict( # noqa: C901
bus2 = buses[line_data["bus2"]]
geometry = Line._parse_geometry(line_data.get("geometry"))
length = line_data["length"]
max_loading = line_data["max_loading"]
lp = lines_params[line_data["params_id"]]
gid = line_data.get("ground")
ground = grounds[gid] if gid is not None else None
line = Line(
id=id, bus1=bus1, bus2=bus2, parameters=lp, phases=phases, length=length, ground=ground, geometry=geometry
id=id,
bus1=bus1,
bus2=bus2,
parameters=lp,
phases=phases,
length=length,
ground=ground,
geometry=geometry,
max_loading=max_loading,
)
if include_results:
line = _assign_branch_currents(branch=line, branch_data=line_data)
Expand All @@ -195,12 +206,22 @@ def network_from_dict( # noqa: C901
id = transformer_data["id"]
phases1 = transformer_data["phases1"]
phases2 = transformer_data["phases2"]
tap = transformer_data["tap"]
max_loading = transformer_data["max_loading"]
bus1 = buses[transformer_data["bus1"]]
bus2 = buses[transformer_data["bus2"]]
geometry = Transformer._parse_geometry(transformer_data.get("geometry"))
tp = transformers_params[transformer_data["params_id"]]
transformer = Transformer(
id=id, bus1=bus1, bus2=bus2, parameters=tp, phases1=phases1, phases2=phases2, geometry=geometry
id=id,
bus1=bus1,
bus2=bus2,
parameters=tp,
phases1=phases1,
phases2=phases2,
tap=tap,
geometry=geometry,
max_loading=max_loading,
)
if include_results:
transformer = _assign_branch_currents(branch=transformer, branch_data=transformer_data)
Expand Down Expand Up @@ -302,13 +323,13 @@ def network_to_dict(en: "ElectricalNetwork", *, include_results: bool) -> JsonDi
line_params: list[JsonDict] = []
for lp in lines_params_dict.values():
line_params.append(lp.to_dict(include_results=include_results))
line_params.sort(key=lambda x: x["id"]) # Always keep the same order
line_params.sort(key=lambda x: (type(x["id"]).__name__, str(x["id"]))) # Always keep the same order

# Transformer parameters
transformer_params: list[JsonDict] = []
for tp in transformers_params_dict.values():
transformer_params.append(tp.to_dict(include_results=include_results))
transformer_params.sort(key=lambda x: x["id"]) # Always keep the same order
transformer_params.sort(key=lambda x: (type(x["id"]).__name__, str(x["id"]))) # Always keep the same order

res = {
"version": NETWORK_JSON_VERSION,
Expand Down Expand Up @@ -629,7 +650,7 @@ def v1_to_v2_converter(data: JsonDict) -> JsonDict:
return results


def v2_to_v3_converter(data: JsonDict) -> JsonDict:
def v2_to_v3_converter(data: JsonDict) -> JsonDict: # noqa: C901
"""Convert a v2 network dict to a v3 network dict.
Args:
Expand Down Expand Up @@ -663,9 +684,17 @@ def v2_to_v3_converter(data: JsonDict) -> JsonDict:
transformers_params = []
transformers_params_max_loading = {}
for transformer_param_data in old_transformers_params:
transformer_param_data["vg"] = transformer_param_data.pop("type")
vg = transformer_param_data.pop("type")
if vg == "single":
vg = "Ii0"
elif vg == "center":
vg = "Iii0"
transformer_param_data["vg"] = vg
if (max_power := transformer_param_data.pop("max_power", None)) is not None:
transformers_params_max_loading[transformer_param_data["id"]] = max_power / transformer_param_data["sn"]
loading = max_power / transformer_param_data["sn"]
else:
loading = 1
transformers_params_max_loading[transformer_param_data["id"]] = loading
transformers_params.append(transformer_param_data)

# Rename `maximal_current` in `ampacities` and uses array
Expand All @@ -676,14 +705,14 @@ def v2_to_v3_converter(data: JsonDict) -> JsonDict:
lines_params = []
for line_param_data in old_lines_params:
size = len(line_param_data["z_line"][0])
if (maximal_current := line_param_data.pop("maximal_current", None)) is not None:
if (maximal_current := line_param_data.pop("max_current", None)) is not None:
line_param_data["ampacities"] = [maximal_current] * size
if (section := line_param_data.pop("section", None)) is not None:
line_param_data["sections"] = [section] * size
if (conductor_type := line_param_data.pop("conductor_types", None)) is not None:
if (conductor_type := line_param_data.pop("conductor_type", None)) is not None:
line_param_data["materials"] = [conductor_type] * size
if (
(insulator_type := line_param_data.pop("insulator_types", None)) is not None
(insulator_type := line_param_data.pop("insulator_type", None)) is not None
) and insulator_type.lower() != "unknown":
line_param_data["insulators"] = [insulator_type] * size
lines_params.append(line_param_data)
Expand All @@ -699,7 +728,7 @@ def v2_to_v3_converter(data: JsonDict) -> JsonDict:
old_transformers = data.get("transformers", [])
transformers = []
for transformer_data in old_transformers:
transformer_data["max_loading"] = transformers_params_max_loading.get(transformer_data["params_id"], 1)
transformer_data["max_loading"] = transformers_params_max_loading[transformer_data["params_id"]]
transformers.append(transformer_data)

results = {
Expand Down
13 changes: 13 additions & 0 deletions roseau/load_flow/io/tests/data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Versioned network JSON files for testing

## JSON files

**DO NOT MODIFY** the JSON files in this directory if the tests fail. Adapt the converter functions
in the `roseau.load_flow.io.dict` module instead.

## Generator scripts

Scripts in the _generators_ directory are used to generate the versioned network JSON files. They
are executed using old versions of rlf.

**DO NOT UPGRADE** these scripts when modifying rlf.
Loading

0 comments on commit 7c062c0

Please sign in to comment.