88
99import json
1010from pathlib import Path
11- from typing import IO , Any , List , Optional , Union , cast
11+ from typing import IO , Any , List , Optional , cast
1212
1313import numpy as np
1414
1717 BatchArray ,
1818 BatchDataset ,
1919 BatchList ,
20- BatchPythonDataset ,
2120 ComponentList ,
2221 Dataset ,
23- ExtraInfo ,
24- Nominal ,
2522 PythonDataset ,
2623 SingleDataset ,
2724 SinglePythonDataset ,
@@ -101,12 +98,13 @@ def convert_list_to_batch_data(list_data: BatchList) -> BatchDataset:
10198 return batch_data
10299
103100
104- def convert_python_to_numpy (data : PythonDataset , data_type : str ) -> Dataset :
101+ def convert_python_to_numpy (data : PythonDataset , data_type : str , ignore_extra : bool = False ) -> Dataset :
105102 """
106103 Convert native python data to internal numpy
107104 Args:
108105 data: data in dict or list
109106 data_type: type of data: input, update, sym_output, or asym_output
107+ ignore_extra: Allow (and ignore) extra attributes in the data
110108
111109 Returns:
112110 A single or batch dataset for power-grid-model
@@ -118,23 +116,27 @@ def convert_python_to_numpy(data: PythonDataset, data_type: str) -> Dataset:
118116 # data for all batches in converted into a proper and compact numpy structure.
119117 if isinstance (data , list ):
120118 list_data = [
121- convert_python_single_dataset_to_single_dataset (json_dict , data_type = data_type ) for json_dict in data
119+ convert_python_single_dataset_to_single_dataset (json_dict , data_type = data_type , ignore_extra = ignore_extra )
120+ for json_dict in data
122121 ]
123122 return convert_list_to_batch_data (list_data )
124123
125124 # Otherwise this should be a normal (non-batch) structure, with a list of objects (dictionaries) per component.
126125 if not isinstance (data , dict ):
127126 raise TypeError ("Data should be either a list or a dictionary!" )
128127
129- return convert_python_single_dataset_to_single_dataset (data = data , data_type = data_type )
128+ return convert_python_single_dataset_to_single_dataset (data = data , data_type = data_type , ignore_extra = ignore_extra )
130129
131130
132- def convert_python_single_dataset_to_single_dataset (data : SinglePythonDataset , data_type : str ) -> SingleDataset :
131+ def convert_python_single_dataset_to_single_dataset (
132+ data : SinglePythonDataset , data_type : str , ignore_extra : bool = False
133+ ) -> SingleDataset :
133134 """
134135 Convert native python data to internal numpy
135136 Args:
136137 data: data in dict
137138 data_type: type of data: input, update, sym_output, or asym_output
139+ ignore_extra: Allow (and ignore) extra attributes in the data
138140
139141 Returns:
140142 A single dataset for power-grid-model
@@ -143,18 +145,23 @@ def convert_python_single_dataset_to_single_dataset(data: SinglePythonDataset, d
143145
144146 dataset : SingleDataset = {}
145147 for component , objects in data .items ():
146- dataset [component ] = convert_component_list_to_numpy (objects = objects , component = component , data_type = data_type )
148+ dataset [component ] = convert_component_list_to_numpy (
149+ objects = objects , component = component , data_type = data_type , ignore_extra = ignore_extra
150+ )
147151
148152 return dataset
149153
150154
151- def convert_component_list_to_numpy (objects : ComponentList , component : str , data_type : str ) -> np .ndarray :
155+ def convert_component_list_to_numpy (
156+ objects : ComponentList , component : str , data_type : str , ignore_extra : bool = False
157+ ) -> np .ndarray :
152158 """
153159 Convert native python data to internal numpy
154160 Args:
155161 objects: data in dict
156162 component: the name of the component
157163 data_type: type of data: input, update, sym_output, or asym_output
164+ ignore_extra: Allow (and ignore) extra attributes in the data
158165
159166 Returns:
160167 A single numpy array
@@ -168,19 +175,19 @@ def convert_component_list_to_numpy(objects: ComponentList, component: str, data
168175 # As each object is a separate dictionary, and the attributes may differ per object, we need to check
169176 # all attributes. Non-existing attributes
170177 for attribute , value in obj .items ():
171- if attribute == "extra" :
172- # The "extra" attribute is a special one. It can store any type of information associated with
173- # an object, but it will not be used in the calculations. Therefore it is not included in the
174- # numpy array, so we can skip this attribute
175- continue
176178
179+ # If an attribute doesn't exist, the user should explicitly state that she/he is ok with extra
180+ # information in the data. This is to protect the user from overlooking errors.
177181 if attribute not in array .dtype .names :
178- # If an attribute doesn't exist, the user made a mistake. Let's be merciless in that case,
179- # for their own good.
180- raise ValueError (f"Invalid attribute '{ attribute } ' for { component } { data_type } data." )
181-
182- # Now just assign the value and raise an error if the value cannot be stored in the specific
183- # numpy array data format for this attribute.
182+ if ignore_extra :
183+ continue
184+ raise ValueError (
185+ f"Invalid attribute '{ attribute } ' for { component } { data_type } data. "
186+ "(Use ignore_extra=True to ignore the extra data and suppress this exception)"
187+ )
188+
189+ # Assign the value and raise an error if the value cannot be stored in the specific numpy array data format
190+ # for this attribute.
184191 try :
185192 array [i ][attribute ] = value
186193 except ValueError as ex :
@@ -419,19 +426,20 @@ def convert_single_dataset_to_python_single_dataset(data: SingleDataset) -> Sing
419426 }
420427
421428
422- def import_json_data (json_file : Path , data_type : str ) -> Dataset :
429+ def import_json_data (json_file : Path , data_type : str , ignore_extra : bool = False ) -> Dataset :
423430 """
424431 import json data
425432 Args:
426433 json_file: path to the json file
427434 data_type: type of data: input, update, sym_output, or asym_output
435+ ignore_extra: Allow (and ignore) extra attributes in the json file
428436
429437 Returns:
430438 A single or batch dataset for power-grid-model
431439 """
432440 with open (json_file , mode = "r" , encoding = "utf-8" ) as file_pointer :
433- json_data = json .load (file_pointer )
434- return convert_python_to_numpy (json_data , data_type )
441+ data = json .load (file_pointer )
442+ return convert_python_to_numpy (data = data , data_type = data_type , ignore_extra = ignore_extra )
435443
436444
437445def import_input_data (json_file : Path ) -> SingleDataset :
@@ -461,29 +469,19 @@ def import_update_data(json_file: Path) -> BatchDataset:
461469 return cast (BatchDataset , import_json_data (json_file = json_file , data_type = "update" ))
462470
463471
464- def export_json_data (
465- json_file : Path ,
466- data : Dataset ,
467- indent : Optional [int ] = 2 ,
468- compact : bool = False ,
469- extra_info : Optional [Union [ExtraInfo , List [ExtraInfo ]]] = None ,
470- ):
472+ def export_json_data (json_file : Path , data : Dataset , indent : Optional [int ] = 2 , compact : bool = False ):
471473 """
472474 export json data
473475 Args:
474476 json_file: path to json file
475477 data: a single or batch dataset for power-grid-model
476478 indent: indent of the file, default 2
477479 compact: write components on a single line
478- extra_info: extra information (in any json-serializable format), indexed on the object ids
479- e.g. a string representing the original id, or a dictionary storing even more information.
480480
481481 Returns:
482482 Save to file
483483 """
484484 json_data = convert_dataset_to_python_dataset (data )
485- if extra_info is not None :
486- inject_extra_info (data = json_data , extra_info = extra_info )
487485
488486 with open (json_file , mode = "w" , encoding = "utf-8" ) as file_pointer :
489487 if compact and indent :
@@ -494,70 +492,14 @@ def export_json_data(
494492 json .dump (json_data , file_pointer , indent = indent )
495493
496494
497- def inject_extra_info (data : PythonDataset , extra_info : Union [ExtraInfo , List [ExtraInfo ]]):
498- """
499- Injects extra info to the objects by ID
500-
501- Args:
502- data: Power Grid Model Python data, as written to pgm json files.
503- extra_info: A dictionary indexed by object id. The value may be anything.
504-
505- """
506- if isinstance (data , list ):
507- _inject_extra_info_batch (data = data , extra_info = extra_info )
508- elif isinstance (data , dict ):
509- _inject_extra_info_single (data = data , extra_info = cast (ExtraInfo , extra_info ))
510- else :
511- raise TypeError ("Invalid data type" )
512-
513-
514- def _inject_extra_info_single (data : SinglePythonDataset , extra_info : ExtraInfo ):
515- """
516- Injects extra info to the objects by ID
517-
518- Args:
519- data: Power Grid Model Python data, as written to pgm json files.
520- extra_info: A dictionary indexed by object id. The value may be anything.
521-
522- """
523- if not isinstance (extra_info , dict ):
524- raise TypeError ("Invalid extra info data type" )
525-
526- for _ , objects in data .items ():
527- for obj in objects :
528- if obj ["id" ] in extra_info :
529- # IDs are always nominal values, so let's tell the type checker:
530- obj_id = cast (Nominal , obj ["id" ])
531- obj ["extra" ] = extra_info [obj_id ]
532-
533-
534- def _inject_extra_info_batch (data : BatchPythonDataset , extra_info : Union [ExtraInfo , List [ExtraInfo ]]):
535- """
536- Injects extra info to the objects by ID
537-
538- Args:
539- data: Power Grid Model Python data, as written to pgm json files.
540- extra_info: A dictionary indexed by object id. The value may be anything.
541-
542- """
543- if isinstance (extra_info , list ):
544- # If both data and extra_info are lists, expect one extra info set per batch
545- for batch , info in zip (data , extra_info ):
546- _inject_extra_info_single (batch , info )
547- else :
548- # If only data is a list, copy extra_info for each batch
549- for batch in data :
550- _inject_extra_info_single (batch , extra_info )
551-
552-
553495def compact_json_dump (data : Any , io_stream : IO [str ], indent : int , max_level : int , level : int = 0 ):
554496 """Custom compact JSON writer that is intended to put data belonging to a single object on a single line.
555497
556498 For example:
557499 {
558500 "node": [
559- {"id": 0, "u_rated": 10500.0, "extra": {"original_id": 123} },
560- {"id": 1, "u_rated": 10500.0, "extra": {"original_id": 456} },
501+ {"id": 0, "u_rated": 10500.0},
502+ {"id": 1, "u_rated": 10500.0},
561503 ],
562504 "line": [
563505 {"id": 2, "node_from": 0, "node_to": 1, ...}
0 commit comments