Tracking Changes in GDM#
The grid-data-models (GDM) package includes support for tracking changes to distribution systems. Edit, addition, and deletion changes are tracked relative to a single base GDM model for specific timestamps. All changes are stored to provide a history of changes over time.
We will use the gdmloader package to first download a sample GDM model.
from gdm.distribution import DistributionSystem
from gdm.quantities import PositiveDistance
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()
/opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:502: UserWarning: Ellipsis is not a Python type (it may be an instance of an object), Pydantic will allow any object with no validation since we cannot even enforce that the input is an instance of the given type. To get rid of this error wrap the type with `pydantic.SkipValidation`.
warn(
System ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ ┃ Property ┃ Value ┃ ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ │ System name │ │ │ Data format version │ 2.0.1 │ │ 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 │ └──────────────────────────────────┴───────┘
Next, we will build a catalog of components that will serve as an equipment library for additions to the base model. The changes will be applied at different times.
from gdm.distribution.equipment import LoadEquipment
from uuid import UUID, uuid4
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 the modification. These UUIDs should exist in the catalog.
Deletions: This is a list attribute that holds the UUIDs of the components deleted in the modification. These UUIDs should exist in the base system model.
Edits: This is a list attribute that holds the
PropertyEditobjects representing the edits made in this modification.PropertyEditrequires the name of the property to be edited, the new value of the property, and thecomponent_uuidthat 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, PositiveDistance is used to define the new value in the example below. Otherwise, there will be a validation error.
from gdm.tracked_changes import PropertyEdit, TrackedChange
from gdm.quantities import Distance
system_changes = [
TrackedChange(
scenario_name="scenario_1",
update_date="2022-01-01",
edits=[
PropertyEdit(
component_uuid=line.uuid,
name="length",
value=Distance(100, "meter"),
)
],
),
TrackedChange(
scenario_name="scenario_1",
update_date="2023-01-01",
additions=["aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"],
),
TrackedChange(
scenario_name="scenario_1",
update_date="2024-01-01",
deletions=[load1.uuid],
),
TrackedChange(
scenario_name="scenario_2",
update_date="2025-01-01",
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",
update_date=datetime.strptime("2022-1-1", "%Y-%m-%d").date(),
)
Finally, we use functions provided by the GDM library to apply changes to the base distribution system model.
from datetime import date
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 │ Edit │ d9c7511e-ec68-48… │ MatrixImpedanceB… │ sourcebus_connec… │ sourcebus │ scenario_1 │ │ │ │ │ │ │ connector │ │ └────────────┴───────────┴───────────────────┴───────────────────┴───────────────────┴───────────────┴────────────┘
new_system.info()
System ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ ┃ Property ┃ Value ┃ ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ │ System name │ │ │ Data format version │ 2.0.1 │ │ 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 updates 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('d9c7511e-ec68-484e-ad3b-f5b0340b3124'))], 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('cb0a4838-bb55-4a6e-80ee-a1a91b3c15dc')])]
Updates applied to the system ┏━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ Timestamp ┃ Operation ┃ UUID ┃ Component Type ┃ Component Name ┃ Connected bus ┃ Scenario ┃ ┡━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩ │ 2022-01-01 │ Edit │ d9c7511e-ec68-48… │ 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 │ cb0a4838-bb55-4a… │ DistributionLoad │ fdr1_load1 │ fdr1_2_load │ scenario_1 │ └────────────┴───────────┴───────────────────┴───────────────────┴───────────────────┴───────────────┴────────────┘
System ┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ ┃ Property ┃ Value ┃ ┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩ │ System name │ │ │ Data format version │ 2.0.1 │ │ 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 │ └──────────────────────────────────┴───────┘