Working with GDM models#
ERAD’s AssetSystem provides a class method for users working with a GDM DistributionSystem. Distribution system components are automatically mapped to the asset models and added to the AssetSystem. A hazard simulation can then be set up using the steps listed above. We start by loading a GDM model using the gdmloader package. This package can be installed using the command
pip install gdmloader
from IPython.display import display, HTML
import plotly.graph_objects as go
import plotly.io as pio
pio.renderers.default = "notebook_connected"
from gdm.distribution import DistributionSystem
from gdmloader.constants import GCS_CASE_SOURCE
from gdmloader.source import SystemLoader
gdm_loader = SystemLoader()
gdm_loader.add_source(GCS_CASE_SOURCE)
distribution_system: DistributionSystem = gdm_loader.load_dataset(
    source_name=GCS_CASE_SOURCE.name,
    system_type=DistributionSystem,
    dataset_name="p1rhs7_1247",
    version="2_1_2",
)
distribution_system.name = "p1rhs7_1247"
distribution_system.info()
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Cell In[1], line 14
     11 gdm_loader = SystemLoader()
     12 gdm_loader.add_source(GCS_CASE_SOURCE)
---> 14 distribution_system: DistributionSystem = gdm_loader.load_dataset(
     15     source_name=GCS_CASE_SOURCE.name,
     16     system_type=DistributionSystem,
     17     dataset_name="p1rhs7_1247",
     18     version="2_1_2",
     19 )
     20 distribution_system.name = "p1rhs7_1247"
     21 distribution_system.info()
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/gdmloader/source.py:129, in SystemLoader.load_dataset(self, system_type, source_name, dataset_name, version)
    126         raise ValueError(msg)
    128 system_file = list(dataset_folder.rglob("*.json"))[0]
--> 129 return system_type.from_json(system_file)
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/infrasys/system.py:221, in System.from_json(cls, filename, upgrade_handler, **kwargs)
    219     data = json.load(f_in)
    220 time_series_parent_dir = Path(filename).parent
--> 221 return cls.from_dict(
    222     data, time_series_parent_dir, upgrade_handler=upgrade_handler, **kwargs
    223 )
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/infrasys/system.py:332, in System.from_dict(cls, data, time_series_parent_dir, upgrade_handler, **kwargs)
    326         upgrade_handler(
    327             system_data,
    328             system_data.get("data_format_version"),
    329             system.data_format_version,
    330         )
    331 system.deserialize_system_attributes(system_data)
--> 332 system._deserialize_components(system_data["components"])
    333 system._deserialize_supplemental_attributes(system_data["supplemental_attributes"])
    334 logger.info("Deserialized system {}", system.label)
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/infrasys/system.py:1428, in System._deserialize_components(self, components)
   1426 skipped_types = self._deserialize_components_first_pass(components, cached_types)
   1427 if skipped_types:
-> 1428     self._deserialize_components_nested(skipped_types, cached_types)
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/infrasys/system.py:1462, in System._deserialize_components_nested(self, skipped_types, cached_types)
   1460 if len(components) > 1:
   1461     for component_dict in components[1:]:
-> 1462         component = self._try_deserialize_component(component_dict, cached_types)
   1463         assert component is not None
   1464 deserialized_types.add(component_type)
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/infrasys/system.py:1484, in System._try_deserialize_component(self, component, cached_types)
   1482 metadata = SerializedTypeMetadata(**component[TYPE_METADATA])
   1483 component_type = cached_types.get_type(metadata.fields)
-> 1484 actual_component = component_type(**values)
   1485 self._components.add(actual_component, deserialization_in_progress=True)
   1486 return actual_component
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/pydantic/main.py:214, in BaseModel.__init__(self, **data)
    212 # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks
    213 __tracebackhide__ = True
--> 214 validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
    215 if self is not validated_self:
    216     warnings.warn(
    217         'A custom validator is returning a value other than `self`.\n'
    218         "Returning anything other than `self` from a top level model validator isn't supported when validating via `__init__`.\n"
    219         'See the `model_validator` docs (https://docs.pydantic.dev/latest/concepts/validators/#model-validators) for more details.',
    220         stacklevel=2,
    221     )
ValidationError: 1 validation error for MatrixImpedanceBranch
  Value error, Length of matrix mat=<Quantity([[1.76096667 0.39596667]
 [0.39596667 1.76096667]], 'ohm / kilometer')> did not match number of phases self.phases=[<Phase.S2: 'S2'>, <Phase.S1: 'S1'>, <Phase.N: 'N'>] [type=value_error, input_value={'uuid': '42a039ae-76dd-4...tity(115.0, 'ampere')>)}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.10/v/value_error
Next, we built the asset system from the gdm DistributionSystem using the from_gdm method.
from erad.systems import AssetSystem
asset_system = AssetSystem.from_gdm(distribution_system)
asset_system.info()
for a in asset_system.iter_all_components():
    a.pprint()
    break
System ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ ┃ Property ┃ Value ┃ ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ │ System name │ │ │ Data format version │ │ │ Components attached │ 4987 │ │ Time Series attached │ 0 │ │ Description │ │ └──────────────────────┴───────┘
Component Information ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ ┃ Type ┃ Count ┃ ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ │ Asset │ 4987 │ └──────────────────────┴───────┘
Asset( name='sb10_p1rhs7_1247_bus_10_6', distribution_asset=UUID('1f95b063-9b69-4331-8f56-6a718161087f'), connections=[], devices=[], asset_type=<AssetTypes.distribution_poles: 6>, height=<Quantity(3, 'meter')>, latitude=38.64000605387684, longitude=-122.50715315149552, asset_state=[], elevation=<Quantity(460.0, 'meter')> )
Plotting an AssetSystem#
fig = asset_system.plot(show=False)
display(HTML(pio.to_html(fig, include_plotlyjs="cdn", full_html=False)))
Asset type:  substation
Asset type:  distribution_poles
Asset type:  distribution_junction_box
Building a HazardModel#
In this section, we built a hazard model and apply the model the asset system.
from datetime import datetime
from shapely.geometry import Polygon
from gdm.quantities import Distance
from erad.models.hazard import FloodModelArea, FloodModel
from erad.systems import HazardSystem
from erad.quantities import Speed
flood_area = FloodModelArea(
    affected_area=Polygon(
        [
            (-122.38, 38.70),
            (-122.35, 38.68),
            (-122.343, 38.69),
            (-122.37, 38.7035),
        ]
    ),
    water_velocity=Speed(0, "meter/second"),
    water_elevation=Distance(160, "meter"),
)
flood = FloodModel(
    name="flood 1",
    timestamp=datetime.now(),
    affected_areas=[flood_area],
)
user_defined_flood_event = HazardSystem(auto_add_composed_components=True)
user_defined_flood_event.add_component(flood)
Overlaying the HazardModel#
We can overlay the hazard model on the same plot using the add_trace method. The show method can be used to render the image again.
polygon = flood.affected_areas[0].affected_area
lon, lat = polygon.exterior.xy  # returns x and y sequences
fig.add_trace(
    go.Scattermap(
        fill="toself",
        lon=lon.tolist(),
        lat=lat.tolist(),
        marker={"size": 10, "color": flood.affected_areas[0].water_velocity.magnitude},
    )
)
fig.show()
Finally, we can run the actual simulation using the HazardSimulator class from erad.runner.
from erad.runner import HazardSimulator
user_defined_flood_event.info()
hazard_scenario = HazardSimulator(asset_system=asset_system)
hazard_scenario.run(hazard_system=user_defined_flood_event)
System ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ ┃ Property ┃ Value ┃ ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ │ System name │ │ │ Data format version │ │ │ Components attached │ 2 │ │ Time Series attached │ 0 │ │ Description │ │ └──────────────────────┴───────┘
Component Information ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ ┃ Type ┃ Count ┃ ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ │ FloodModel │ 1 │ │ FloodModelArea │ 1 │ └──────────────────────┴───────┘
2025-07-18 16:06:49.885 | WARNING  | erad.runner:run:51 - No HazardFragilityCurves definations found in the passed HazardSystem using default curve definations
Once the simulation is complete, we can visualize the results by plotting the survival_prob from the updated gdf dataframe.
fig = asset_system.plot(show=False)
display(HTML(pio.to_html(fig, include_plotlyjs="cdn", full_html=False)))
Asset type:  substation
Asset type:  distribution_poles
Asset type:  distribution_junction_box
