Version 2.3.6¶
Tap positions on transformer-based components¶
Added tap_positions: list[list[float]] | None to DistributionTransformerBase, inherited by both DistributionTransformer and DistributionRegulator:
Stores the current per-unit tap position for each phase in each winding, matching the shape of
winding_phases.Defaults to
None; the model validator auto-populates[[1.0, ...], ...]when not provided, ensuring backward compatibility with serialized data.Includes shape validation: number of entries must match windings, and each entry’s length must match its corresponding winding_phases.
Capacitor state on DistributionCapacitor¶
Added state: list[bool] and a computed num_banks property to DistributionCapacitor:
state— indicates whether each bank is on (True) or off (False).num_banks— computed field returninglen(self.state).
Aggregate parallel single-phase transformers¶
New utility aggregate_single_phase_transformers(system) in gdm.distribution.utils:
Finds groups of 3 single-phase
DistributionTransformerorDistributionRegulatorcomponents connected to the same buses.Merges each group into a single three-phase equivalent (combining winding phases, tap positions, rated power, and controllers).
Removes originals and adds the merged component to the system.
Migration handler¶
Added
from__2_3_5__to__2_3_6upgrade handler that populatestap_positions(default1.0) on existing transformer/regulator data andstate(default allTrue) on existing capacitor data.
from gdm.distribution.components import DistributionTransformer, DistributionRegulator, DistributionCapacitor
# Transformer with explicit tap positions
xfmr = DistributionTransformer.example()
print("Transformer tap_positions:", xfmr.tap_positions)
# Regulator with explicit tap positions
reg = DistributionRegulator.example()
print("Regulator tap_positions:", reg.tap_positions)
# Capacitor with state and computed num_banks
cap = DistributionCapacitor.example()
print("Capacitor state:", cap.state)
print("Capacitor num_banks:", cap.num_banks)Version 2.3.5¶
New GeometrySwitch component¶
A new distribution component for switchable geometry-based branches:
GeometrySwitch: ExtendsDistributionSwitchBase— represents a geometry-defined branch with switch (open/close) capability. Includesis_closed: list[bool]per phase andto_matrix_representation()which returns aMatrixImpedanceSwitch.GeometryBranchremains onDistributionBranchBasefor non-switchable lines, providing clean separation between switchable and non-switchable geometry branches.
Dependency updates¶
Updated
infrasysrequirement from~=1.1to~=1.2.Updated
pandasrequirement from~=3.0.2to~=3.0.3.Updated
jupyter-bookrequirement from>=2.1.4,<3to>=2.1.5,<3.
from gdm.distribution.components import GeometrySwitch
GeometrySwitch.example().pprint()Version 2.3.4¶
Wholesale market tariff models¶
New data models for wholesale electricity market tariff structures, enabling representation of ISO/RTO market settlement:
LMPRate: Locational Marginal Price broken into energy, congestion, and loss components with atotal_lmpproperty.PricingNode: Represents a pricing node (PNode) — supports load zone, generator node, hub, and interface types.AncillaryServiceRate: Rate for ancillary service products (regulation, spinning reserve, frequency response, etc.) scoped to day-ahead or real-time markets.CapacityPayment: Capacity market payment structure (forward capacity, reliability must-run, resource adequacy) with month-scoped commitments.TransmissionServiceCharge: Charge for transmission network service.WholesaleMarketTariff: Top-level tariff combining LMP, ancillary services, capacity payments, and transmission charges at a specific pricing node. Includes a validator to prevent duplicate ancillary service entries.
Aggregator models¶
New data models for resource aggregation in wholesale and demand response markets:
AggregatedDER: Wraps aDistributionBatteryorDistributionSolarwith its committed capacity.AggregatedLoad: Wraps aDistributionLoadwith curtailable capacity and demand response program type.DERAggregator: Bundles DERs for wholesale market participation — tracks offered ancillary services, dispatch limits, and an optionalWholesaleMarketTariff.LoadAggregator: Bundles loads for demand response programs — tracks curtailable capacity, notification lead time, max curtailment duration, and an optionalDistributionTariff.Aggregator: General-purpose aggregator that can hold both DER and load resources with optional wholesale and retail tariffs.
New enums¶
WholesaleMarketType— day-ahead, real-timeAncillaryServiceType— regulation up/down, spinning/non-spinning reserve, frequency response, reactive supplyCapacityMarketType— forward capacity, reliability must-run, resource adequacyPricingNodeType— load zone, generator node, hub, interfaceAggregatorType— DER, load, genericDemandResponseType— economic, emergency, direct load control, interruptible
Bug fixes¶
Issue #179: Fixed load phase power distribution in
_get_load_power_per_phase— when using actual time series data, total power is now correctly divided by the number of phases instead of being assigned to each phase at full value.Issue #179: Added input validation in
_get_combined_time_series_df— raisesValueErrorwhenaggregate_phases=Falseis set without providing aper_phase_function.
from gdm.distribution.market import WholesaleMarketTariff
WholesaleMarketTariff.example().pprint()from gdm.distribution.market.aggregator import DERAggregator
DERAggregator.example().pprint()Version 2.3.3¶
Tariff model improvements¶
Several enhancements to tariff models for more flexible rate and charge definitions:
SeasonalTOURates:seasonrenamed tomonthsand now acceptsList[Month], allowing a single entry to cover multiple months (e.g., summer =[JUNE, JULY, AUGUST]). Includes a duplicate-month validator.DemandCharge: Addedmonths: List[Month]field so demand charges can be scoped to specific months for seasonal variation. Includes a duplicate-month validator.FixedCharge:amountconstraint relaxed fromgt=0toge=0, allowing$0fixed charges.DistributionTariff:fixed_chargeis now optional (defaults toNone). Added overlapping-month validator acrossseasonal_touentries to ensure no month appears in more than oneSeasonalTOURates.
Dependency updates¶
Updated
pandasrequirement from~=2.2.3to~=3.0.2.Updated
mcprequirement from>=1.0.0to>=1.27.0.Updated
jupyter-bookrequirement from>=2,<3to>=2.1.4,<3.
Version 2.3.2¶
MCP server enhancements¶
The MCP integration includes expanded tooling and better runtime control:
Added
reduce_systemandsave_systemoperations for model reduction and export workflows.Added runtime tool gating with
set_tool_calls_enabledandget_tool_calls_enabled.Added
model_refinteroperability for path-based tools (direct path fields or registry-backed lookup).
Tariff model update¶
Time-of-use tariff season tagging uses month-based tagging:
SeasonalTOURates.seasonnow usesMonthenum values.Examples now use explicit month values (for example,
Month.JULY,Month.JANUARY).
Minor bug fixes¶
This patch release also includes targeted stability fixes:
Issue #163: Adjusted bus/branch phase validation to avoid false errors in multigraph cases (for example, parallel single-phase branches between three-phase buses) and neutral-handling edge cases.
Issue #112: Reduced repeated reducer log spam by avoiding repeated graph/warning emissions during reduction workflows.
Issue #114: Standardized naming from
timeseriestotime_seriesacross APIs and docs for consistency with infrasys conventions.Issue #157: Fixed capacitor aggregation edge cases to prevent division-by-zero when all resistance or reactance inputs are zero.
Documentation updates¶
MCP docs and release notes were refreshed to reflect the current tooling and bug-fix behavior.
Version 2.3.0¶
Introduction of MCP (Model Context Protocol) server¶
This release marks the introduction of a new Model Context Protocol (MCP) server for GDM, enabling AI agents and language models to interact with grid-data-models through standardized tools:
gdm-mcp-server: A new CLI entry point that exposes GDM functionality as 20+ MCP tools.Core tool categories:
System diagnosis and fixing (analyze, diagnose, apply fixes, suggest improvements)
System operations (merge, split by feeder/substation, reduce complexity)
System inspection (query components, get relationships, analyze topology)
Time-series and subsystem management (export by buses, time series summary)
Documentation and knowledge access (search GDM docs, get code examples, API references)
User impact¶
GDM can now be integrated with AI tools and agents that support the MCP protocol.
Claude Desktop and other MCP-compatible clients can connect to the server for enhanced grid analysis workflows.
Useful for automated system diagnostics, batch model processing, and programmatic access via language models.
Version 2.2.0¶
Infrastructure and dependency migration¶
This release focused on migration and compatibility work required for the next feature cycle:
Upgraded to
infrasys~=1.0and aligned core distribution-system code paths with the new InfraSys interfaces.Added/updated upgrade-handler migration support from
2.1.5to2.2.0(migrate_component_metadata) to preserve component counts while normalizing metadata.Applied dependency compatibility fixes for Pydantic and related packaging/versioning updates.
Updated test matrix and release/deploy workflow pieces used by CI.
Included docs/build fixes (including Jupyter Book deployment/link corrections).
User impact¶
Existing models can still be loaded through upgrade handlers when moving from 2.1.x to 2.2.x.
Most changes are internal compatibility and release engineering updates rather than new end-user APIs.
Version 2.1.6¶
Release and packaging update¶
This release was a lightweight patch release focused on release automation:
Updated deployment workflow configuration (
.github/workflows/deploy.yml).
User impact¶
No major model/API behavior changes were introduced in this tag.
Primarily improves packaging/release pipeline reliability.
Version 2.2.1¶
Deterministic directed graph construction¶
Recent fixes make the DistributionSystem.get_directed_graph behaviour deterministic:
DFS traversal is now deterministic: neighbors and parallel edges are iterated in sorted order.
Cycle pruning is deterministic: when producing a radial network the edge removed from each cycle is chosen by lexicographic edge-name ordering (with tie-breakers).
These changes remove flakiness from tests that assert exact node/edge counts or specific pruned edges.
pytest tests/test_distribution_graph.py::test_directed_graph_multiple_loops_pruning -qIf you rely on a specific choice of which parallel edge is used in analysis, prefer to inspect the undirected graph (get_undirected_graph) or explicitly set component names to control pruning selection.
Matrix impedance calculation fixes¶
Correct unit handling and length scaling¶
Fixed issues in GeometryBranch.to_matrix_representation and convert_geometry_to_matrix_representation where unit conversion and length scaling could produce incorrect impedance magnitudes. The conversion now consistently uses the system’s unit definitions and converts lengths before computing per-unit matrices.
Kron reduction stability improvements¶
Improved numerical stability in MatrixImpedanceBranch.kron_reduce() to avoid introducing small imaginary/real artifacts during network reduction.
Notes for users¶
If you previously converted geometry branches, re-run
convert_geometry_to_matrix_representation()to pick up corrected impedance values.See
tests/test_impedence_calc.pyfor unit tests covering these fixes.
Version 2.1.5¶
Graph bug fix¶
The graphs returned from GDM are type MultiGraph and MultiDiGraph. This allows user to represent multiple single-phase components as parallel edges in the graph.
GDF Bug Fix¶
DistributionSystem now uses system CRS when exporting to GeoDataFrame format.
Three-phase Model Reduction Algorithm Bug Fix¶
Bug fixes in the three-phase model reduction algorithm
from gdm.distribution.market import DistributionTariff
DistributionTariff.example().pprint()Version 2.1.3¶
Plotting bug fix¶
Plotting was broken after the last release. The bug has now been fixed.
Tariff model added¶
The tariff model can now be imported using the code snippet below. In a future release, we will add model representations for aggregators and other market models.
Version 2.1.2¶
Support to line impedance calculations¶
GeometryBranch models can now be converted to MatrixImpedanceBranch model representation using the to_matrix_representation method.
from gdm.distribution.components import GeometryBranch, MatrixImpedanceBranch
g = GeometryBranch.example()
h: MatrixImpedanceBranch = g.to_matrix_representation() All GeometryBranch models in a given system can noe be replaced with MatrixImpedanceBranch model representation using the convert_geometry_to_matrix_representation method for DistributionSystem objects.
from gdm.distribution import DistributionSystem
sys = DistributionSystem.from_json(filename=<path to model>)
sys.convert_geometry_to_matrix_representation()Change to TrackChange object¶
update_date has been changed to timestamp. Now requires datetime object rather than date object..
class TrackedChange(InfraSysBaseModel):
.
.
.
update_date: Annotated[
timestamp | None,
Field(
None,
description="If these changes are to be applied on specific timestamp, provide a timestamp, else leave it blank",
),
]
.
.
.Version 2.1.0¶
Improved validation for physical quantities¶
All Positive quantities have been removed. Additional constraints are now applied using Pydantic Field class. Reduces the number of class definations for physical quantities significantly.
Initial implementation to manage backward compatability for DistributionSystems¶
Going forward (v2.0.0 onwards), users will be able to load older models with newer GDM installations by passing the upgrade handler object, when loading the model from a json file.
from gdm.distribution import DistributionSystem
from gdm.distribution.upgrade_handler.upgrade_handler import UpgradeHandle
upgrade_handler = UpgradeHandler()
DistributionSystem.from_json(filename=<path to model>)
upgrade_handler=upgrade_handler.upgrade)Support to create a deepcopy of GDM system¶
Added functionality to easily create a deep copy of any GDM system object, ensuring that modifications to the copy do not affect the original instance.
from gdm.distribution import DistributionSystem
system = DistributionSystem.from_json(filename=<path to model>)
system_copy = system.deepcopy()