Tracking Changes in GDM

Tracking Changes in GDM#

The grid-data-models (GDM) package includes comprehensive support for modeling tracked changes within a distribution system. This functionality allows users to effectively manage changes to a base grid model, enabling dynamic analysis and scenario planning. All tracked changes are built upon a single base GDM model, ensuring a consistent foundation for analysis. The system allows for edits, additions, and deletions to a base GDM model at specific timestamps. Each modification is tracked and stored, providing a clear history of changes over time.

We will use the gdmloader package to first download a sample GDM model.

from gdm.distribution import DistributionSystem
from gdmloader.constants import GCS_CASE_SOURCE
from gdmloader.source import SystemLoader

loader = SystemLoader()
loader.add_source(GCS_CASE_SOURCE)
base_model: DistributionSystem = loader.load_dataset(
    DistributionSystem, GCS_CASE_SOURCE.name, "three_feeder_switch"
)
base_model.auto_add_composed_components = True
base_model.info()
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/gdmloader/source.py:119, in SystemLoader.load_dataset(self, system_type, source_name, dataset_name, version)
    118 try:
--> 119     source.fs.get(
    120         remote_folder,
    121         str(local_folder),
    122         recursive=True,
    123     )
    124 except FileNotFoundError:

File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/fsspec/asyn.py:118, in sync_wrapper.<locals>.wrapper(*args, **kwargs)
    117 self = obj or args[0]
--> 118 return sync(self.loop, func, *args, **kwargs)

File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/fsspec/asyn.py:103, in sync(loop, func, timeout, *args, **kwargs)
    102 elif isinstance(return_result, BaseException):
--> 103     raise return_result
    104 else:

File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/fsspec/asyn.py:56, in _runner(event, coro, result, timeout)
     55 try:
---> 56     result[0] = await coro
     57 except Exception as ex:

File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/fsspec/asyn.py:645, in AsyncFileSystem._get(self, rpath, lpath, recursive, callback, maxdepth, **kwargs)
    644 rpath = self._strip_protocol(rpath)
--> 645 rpaths = await self._expand_path(
    646     rpath, recursive=recursive, maxdepth=maxdepth
    647 )
    648 if source_is_str and (not recursive or maxdepth is not None):
    649     # Non-recursive glob does not copy directories

File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/fsspec/asyn.py:883, in AsyncFileSystem._expand_path(self, path, recursive, maxdepth)
    882 if isinstance(path, str):
--> 883     out = await self._expand_path([path], recursive, maxdepth)
    884 else:

File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/fsspec/asyn.py:912, in AsyncFileSystem._expand_path(self, path, recursive, maxdepth)
    911 if not out:
--> 912     raise FileNotFoundError(path)
    913 return sorted(out)

FileNotFoundError: ['gdm_data/data/DistributionSystem/2_1_4/three_feeder_switch']

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
Cell In[1], line 7
      5 loader = SystemLoader()
      6 loader.add_source(GCS_CASE_SOURCE)
----> 7 base_model: DistributionSystem = loader.load_dataset(
      8     DistributionSystem, GCS_CASE_SOURCE.name, "three_feeder_switch"
      9 )
     10 base_model.auto_add_composed_components = True
     11 base_model.info()

File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/gdmloader/source.py:126, in SystemLoader.load_dataset(self, system_type, source_name, dataset_name, version)
    124     except FileNotFoundError:
    125         msg = f"{remote_folder=} not found! Check the URL: {source.url}/{remote_folder}"
--> 126         raise ValueError(msg)
    128 system_file = list(dataset_folder.rglob("*.json"))[0]
    129 return system_type.from_json(system_file)

ValueError: remote_folder='gdm_data/data/DistributionSystem/2_1_4/three_feeder_switch' not found! Check the URL: https://storage.googleapis.com/gdm_data/gdm_data/data/DistributionSystem/2_1_4/three_feeder_switch

Next, we will build a catalog of components that will serve as an equipment library for additions to the base model and map to temporal changes in the base model. In this catalog, we add two example models with fixed UUIDs that will later be used for temporal mapping.

from gdm.distribution.equipment import LoadEquipment
from uuid import UUID

catalog = DistributionSystem(auto_add_composed_components=True)
load_equipment = LoadEquipment.example().model_copy(
    update={
        "uuid": UUID("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
        "name": "added_phase_load_model",
    }
)
catalog.add_component(load_equipment)

From the model, we get a line model and two load models. These components will be modified to reflect changes in the base model.

from gdm.distribution.components import DistributionLoad, MatrixImpedanceBranch

line = next(base_model.get_components(MatrixImpedanceBranch))
load1, load2 = list(base_model.get_components(DistributionLoad))[:2]

Each TrackedChange object has a scenario name. Additionally, this object may have a date field that can be used to filter changes based on specific dates. Each TrackedChange includes a list of system additions, edits, and deletions to be applied on the specified date.

  • Additions: This is a list attribute that holds the UUIDs of the components added in this modification. These UUIDs should exist in the catalog.

  • Deletions: This is a list attribute that holds the UUIDs of the components deleted in this modification. These UUIDs should exist in the base system model.

  • Edits: This is a list attribute that holds the PropertyEdit objects representing the edits made in this modification. PropertyEdit requires the name of the property to be edited, the new value of the property, and the component_uuid that maps to the modified component.

Warning

When editing the property of an existing component, ensure you use the same quantity/component type as defined in the model definition. For example, when modifying the length property of a distribution branch, Distance is used to define the new value in the example below.

from gdm.tracked_changes import PropertyEdit, TrackedChange
from gdm.quantities import Distance

system_changes = [
    TrackedChange(
        scenario_name="scenario_1",
        timestamp="2022-01-01 00:00:00",
        edits=[
            PropertyEdit(
                component_uuid=line.uuid,
                name="length",
                value=Distance(100, "meter"),
            )
        ],
    ),
    TrackedChange(
        scenario_name="scenario_1",
        timestamp="2023-01-01 00:00:00",
        additions=["aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"],
    ),
    TrackedChange(
        scenario_name="scenario_1",
        timestamp="2024-01-01 00:00:00",
        deletions=[load1.uuid],
    ),
    TrackedChange(
        scenario_name="scenario_2",
        timestamp="2025-01-01 00:00:00",
        deletions=[load2.uuid],
    ),
]

Next, we use filter_tracked_changes_by_name_and_date to filter a list of tracked changes based on a specific scenario name and / or update date.

from gdm.tracked_changes import filter_tracked_changes_by_name_and_date
from datetime import datetime

tracked_changes = filter_tracked_changes_by_name_and_date(
    system_changes,
    scenario_name="scenario_1",
    timestamp=datetime.strptime("2022-1-1", "%Y-%m-%d"),
)

Finally, we use functions provided by the GDM library to apply changes to the base distribution system model.

from gdm.tracked_changes import apply_updates_to_system

new_system = apply_updates_to_system(
    tracked_changes=tracked_changes, system=base_model, catalog=catalog
)
                                           Updates applied to the system                                           
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃           Timestamp  Operation            UUID  Component Type  Component Name  Connected bus    Scenario ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ 2022-01-01 00:00:00  Edit       708915df-8348…  MatrixImpedan…  sourcebus_con…      sourcebus  scenario_1 │
│                                                                                     connector             │
└─────────────────────┴───────────┴────────────────┴────────────────┴────────────────┴───────────────┴────────────┘
new_system.info()
System                          
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓
┃ Property              Value ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩
│ System name          │       │
│ Data format version  │ 2.1.3 │
│ Components attached  │   437 │
│ Time Series attached │     0 │
│ Description          │       │
└──────────────────────┴───────┘
Component Information                       
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓
┃ Type                              Count ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩
│ DistributionBus                  │    86 │
│ DistributionLoad                 │    45 │
│ DistributionTransformer          │    21 │
│ DistributionTransformerEquipment │     5 │
│ DistributionVoltageSource        │     1 │
│ LoadEquipment                    │    45 │
│ Location                         │     1 │
│ MatrixImpedanceBranch            │    55 │
│ MatrixImpedanceBranchEquipment   │    10 │
│ MatrixImpedanceSwitch            │    12 │
│ MatrixImpedanceSwitchEquipment   │     1 │
│ PhaseLoadEquipment               │   135 │
│ PhaseVoltageSourceEquipment      │     3 │
│ VoltageLimitSet                  │     6 │
│ VoltageSourceEquipment           │     1 │
│ WindingEquipment                 │    10 │
└──────────────────────────────────┴───────┘

Note

update_date date is an optional field. If no date is passed, all scenario_1 updated are applied to the base system.

tracked_changes = filter_tracked_changes_by_name_and_date(
    system_changes,
    scenario_name="scenario_1",
)
print(tracked_changes)
new_system = apply_updates_to_system(
    tracked_changes=tracked_changes, system=base_model, catalog=catalog
)
new_system.info()
[TrackedChange(scenario_name='scenario_1', update_date=datetime.date(2022, 1, 1), additions=[], edits=[PropertyEdit(name='length', value=<Quantity(100, 'meter')>, component_uuid=UUID('8f98b0f7-e4ff-4ac4-a4c7-d76b9d7a15fa'))], deletions=[]), TrackedChange(scenario_name='scenario_1', update_date=datetime.date(2023, 1, 1), additions=[UUID('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')], edits=[], deletions=[]), TrackedChange(scenario_name='scenario_1', update_date=datetime.date(2024, 1, 1), additions=[], edits=[], deletions=[UUID('2aac110f-d0b7-4db0-b356-6bd7d6f3487b')])]
                                           Updates applied to the system                                           
┏━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃  Timestamp  Operation               UUID     Component Type     Component Name  Connected bus    Scenario ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ 2022-01-01  Edit       8f98b0f7-e4ff-4a…  MatrixImpedanceB…  sourcebus_connec…      sourcebus  scenario_1 │
│                                                                                     connector             │
│ 2023-01-01  Addition   aaaaaaaa-aaaa-aa…      LoadEquipment  added_phase_load…           None  scenario_1 │
│ 2024-01-01  Deletion   2aac110f-d0b7-4d…   DistributionLoad         fdr1_load1    fdr1_2_load  scenario_1 │
└────────────┴───────────┴───────────────────┴───────────────────┴───────────────────┴───────────────┴────────────┘
System                          
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓
┃ Property              Value ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩
│ System name          │       │
│ Data format version  │ 2.0.0 │
│ Components attached  │   434 │
│ Time Series attached │     0 │
│ Description          │       │
└──────────────────────┴───────┘
Component Information                       
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓
┃ Type                              Count ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩
│ DistributionBus                  │    86 │
│ DistributionLoad                 │    44 │
│ DistributionTransformer          │    21 │
│ DistributionTransformerEquipment │     5 │
│ DistributionVoltageSource        │     1 │
│ LoadEquipment                    │    45 │
│ Location                         │     1 │
│ MatrixImpedanceBranch            │    55 │
│ MatrixImpedanceBranchEquipment   │    10 │
│ MatrixImpedanceSwitch            │    12 │
│ MatrixImpedanceSwitchEquipment   │     1 │
│ PhaseLoadEquipment               │   133 │
│ PhaseVoltageSourceEquipment      │     3 │
│ VoltageLimitSet                  │     6 │
│ VoltageSourceEquipment           │     1 │
│ WindingEquipment                 │    10 │
└──────────────────────────────────┴───────┘