grid-data-models 2.3.4__tar.gz → 2.3.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/PKG-INFO +3 -3
  2. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/README.md +1 -1
  3. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/pyproject.toml +1 -1
  4. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/__init__.py +1 -0
  5. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/base/distribution_transformer_base.py +30 -0
  6. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/distribution_capacitor.py +16 -3
  7. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/distribution_regulator.py +1 -0
  8. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/distribution_transformer.py +1 -0
  9. grid_data_models-2.3.6/src/gdm/distribution/components/geometry_switch.py +122 -0
  10. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/distribution_system.py +2 -1
  11. grid_data_models-2.3.6/src/gdm/distribution/upgrade_handler/from__2_3_4__to__2_3_5.py +15 -0
  12. grid_data_models-2.3.6/src/gdm/distribution/upgrade_handler/from__2_3_5__to__2_3_6.py +70 -0
  13. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/upgrade_handler.py +12 -0
  14. grid_data_models-2.3.6/src/gdm/distribution/utils/__init__.py +5 -0
  15. grid_data_models-2.3.6/src/gdm/distribution/utils/aggregate_components.py +181 -0
  16. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/version.py +1 -1
  17. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/.gitignore +0 -0
  18. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/LICENSE.txt +0 -0
  19. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/__init__.py +0 -0
  20. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/cli/cli.py +0 -0
  21. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/cli/reducer.py +0 -0
  22. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/constants.py +0 -0
  23. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/dataset/__init__.py +0 -0
  24. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/dataset/cost_model.py +0 -0
  25. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/dataset/dataset_system.py +0 -0
  26. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/__init__.py +0 -0
  27. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/catalog_system.py +0 -0
  28. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/common/__init__.py +0 -0
  29. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/common/curve.py +0 -0
  30. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/common/limitset.py +0 -0
  31. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/common/sequence_pair.py +0 -0
  32. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/base/__init__.py +0 -0
  33. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/base/distribution_branch_base.py +0 -0
  34. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/base/distribution_component_base.py +0 -0
  35. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/base/distribution_switch_base.py +0 -0
  36. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/distribution_battery.py +0 -0
  37. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/distribution_bus.py +0 -0
  38. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/distribution_feeder.py +0 -0
  39. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/distribution_load.py +0 -0
  40. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/distribution_solar.py +0 -0
  41. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/distribution_substation.py +0 -0
  42. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/distribution_vsource.py +0 -0
  43. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/geometry_branch.py +0 -0
  44. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/matrix_impedance_branch.py +0 -0
  45. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/matrix_impedance_fuse.py +0 -0
  46. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/matrix_impedance_recloser.py +0 -0
  47. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/matrix_impedance_switch.py +0 -0
  48. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/components/sequence_impedance_branch.py +0 -0
  49. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/controllers/__init__.py +0 -0
  50. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/controllers/base/__init__.py +0 -0
  51. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/controllers/base/capacitor_controller_base.py +0 -0
  52. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/controllers/base/inverter_controller_base.py +0 -0
  53. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/controllers/distribution_capacitor_controller.py +0 -0
  54. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/controllers/distribution_inverter_controller.py +0 -0
  55. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/controllers/distribution_recloser_controller.py +0 -0
  56. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/controllers/distribution_regulator_controller.py +0 -0
  57. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/controllers/distribution_switch_controller.py +0 -0
  58. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/distribution_graph.py +0 -0
  59. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/enums.py +0 -0
  60. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/__init__.py +0 -0
  61. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/bare_conductor_equipment.py +0 -0
  62. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/base/__init__.py +0 -0
  63. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/base/matrix_impedance_branch_equipment_base.py +0 -0
  64. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/battery_equipment.py +0 -0
  65. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/capacitor_equipment.py +0 -0
  66. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/concentric_cable_equipment.py +0 -0
  67. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/distribution_transformer_equipment.py +0 -0
  68. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/geometry_branch_equipment.py +0 -0
  69. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/inverter_equipment.py +0 -0
  70. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/load_equipment.py +0 -0
  71. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/matrix_impedance_branch_equipment.py +0 -0
  72. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/matrix_impedance_fuse_equipment.py +0 -0
  73. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/matrix_impedance_recloser_equipment.py +0 -0
  74. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/matrix_impedance_switch_equipment.py +0 -0
  75. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/phase_capacitor_equipment.py +0 -0
  76. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/phase_load_equipment.py +0 -0
  77. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/phase_voltagesource_equipment.py +0 -0
  78. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/recloser_controller_equipment.py +0 -0
  79. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/sequence_impedance_branch_equipment.py +0 -0
  80. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/solar_equipment.py +0 -0
  81. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/equipment/voltagesource_equipment.py +0 -0
  82. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/market/__init__.py +0 -0
  83. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/market/aggregator.py +0 -0
  84. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/market/tariff.py +0 -0
  85. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/model_reduction/__init__.py +0 -0
  86. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/model_reduction/reducer.py +0 -0
  87. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/sys_functools.py +0 -0
  88. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/__init__.py +0 -0
  89. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_0_1__to__2_1_2.py +0 -0
  90. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_1_2__to__2_1_3.py +0 -0
  91. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_1_3__to__2_1_4.py +0 -0
  92. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_1_4__to__2_1_5.py +0 -0
  93. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_1_5__to__2_2_0.py +0 -0
  94. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_2_0__to__2_2_1.py +0 -0
  95. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_2_1__to__2_3_0.py +0 -0
  96. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_3_0__to__2_3_1.py +0 -0
  97. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_3_1__to__2_3_2.py +0 -0
  98. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_3_2__to__2_3_3.py +0 -0
  99. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/distribution/upgrade_handler/from__2_3_3__to__2_3_4.py +0 -0
  100. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/exceptions.py +0 -0
  101. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/hashing_utils.py +0 -0
  102. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/__init__.py +0 -0
  103. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/exceptions.py +0 -0
  104. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/inspection/__init__.py +0 -0
  105. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/inspection/inspector.py +0 -0
  106. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/inspection/relationships.py +0 -0
  107. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/inspection/topology.py +0 -0
  108. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/knowledge/__init__.py +0 -0
  109. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/knowledge/documentation.py +0 -0
  110. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/operations/__init__.py +0 -0
  111. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/operations/merger.py +0 -0
  112. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/operations/splitter.py +0 -0
  113. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/schemas.py +0 -0
  114. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/server.py +0 -0
  115. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/utilities/__init__.py +0 -0
  116. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/utilities/subsystem.py +0 -0
  117. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/utilities/timeseries.py +0 -0
  118. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/validation/__init__.py +0 -0
  119. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/validation/auto_fixer.py +0 -0
  120. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/validation/diagnostics.py +0 -0
  121. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/validation/suggestions.py +0 -0
  122. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/mcp/version.py +0 -0
  123. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/quantities.py +0 -0
  124. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/__init__.py +0 -0
  125. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/components/__init__.py +0 -0
  126. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/components/base.py +0 -0
  127. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/components/building.py +0 -0
  128. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/components/overhead_line_segment.py +0 -0
  129. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/components/pole.py +0 -0
  130. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/components/pvsystem.py +0 -0
  131. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/components/transformer.py +0 -0
  132. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/components/underground_cable.py +0 -0
  133. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/components/underground_junction.py +0 -0
  134. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/structural/structural_system.py +0 -0
  135. {grid_data_models-2.3.4 → grid_data_models-2.3.6}/src/gdm/tracked_changes.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grid-data-models
3
- Version: 2.3.4
3
+ Version: 2.3.6
4
4
  Project-URL: Documentation, https://github.com/NREL-Distribution-Suites/grid-data-models#readme
5
5
  Project-URL: Issues, https://github.com/NREL-Distribution-Suites/grid-data-models/issues
6
6
  Project-URL: Source, https://github.com/NREL-Distribution-Suites/grid-data-models
@@ -36,12 +36,12 @@ Requires-Dist: pydata-sphinx-theme; extra == 'doc'
36
36
  Requires-Dist: sphinx; extra == 'doc'
37
37
  Requires-Dist: sphinxcontrib-mermaid; extra == 'doc'
38
38
  Provides-Extra: mcp
39
- Requires-Dist: mcp>=1.27.2; extra == 'mcp'
39
+ Requires-Dist: mcp>=1.28.0; extra == 'mcp'
40
40
  Description-Content-Type: text/markdown
41
41
 
42
42
 
43
43
 
44
- [![Upload to PyPi](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/publish_to_pypi.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/publish_to_pypi.yml) • [![Pytest](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/pull_request_tests.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/pull_request_tests.yml) • [![deploy-book](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/deploy.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/deploy.yml) • ![PyPI - Downloads](https://img.shields.io/pypi/dm/grid-data-models) • [![codecov](https://codecov.io/github/NLR-Distribution-Suite/grid-data-models/branch/main/graph/badge.svg?token=K0X11EXOX8)](https://codecov.io/github/NLR-Distribution-Suite/grid-data-models) • [![CodeFactor](https://www.codefactor.io/repository/github/nlr-distribution-suite/grid-data-models/badge)](https://www.codefactor.io/repository/github/nlr-distribution-suite/grid-data-models) • ![MCP Server](https://img.shields.io/badge/MCP_Server-enabled-brightgreen) • ![MCP Tools](https://img.shields.io/badge/MCP_Tools-20-blue) • [![GitHub issues](https://img.shields.io/github/issues/NLR-Distribution-Suite/grid-data-models)](https://github.com/NLR-Distribution-Suite/grid-data-models/issues) • [![License](https://img.shields.io/github/license/NLR-Distribution-Suite/grid-data-models)](https://github.com/NLR-Distribution-Suite/grid-data-models/blob/main/LICENSE.txt) • [![PyPI Downloads](https://static.pepy.tech/personalized-badge/grid-data-models?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/grid-data-models)
44
+ [![Upload to PyPi](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/publish_to_pypi.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/publish_to_pypi.yml) • [![Pytest](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/pull_request_tests.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/pull_request_tests.yml) • [![deploy-book](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/deploy.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/deploy.yml) • ![PyPI - Downloads](https://img.shields.io/pypi/dm/grid-data-models) • [![codecov](https://codecov.io/github/NLR-Distribution-Suite/grid-data-models/branch/main/graph/badge.svg?token=K0X11EXOX8)](https://codecov.io/github/NLR-Distribution-Suite/grid-data-models) • [![CodeFactor](https://www.codefactor.io/repository/github/nlr-distribution-suite/grid-data-models/badge)](https://www.codefactor.io/repository/github/nlr-distribution-suite/grid-data-models) • ![MCP Server](https://img.shields.io/badge/MCP_Server-enabled-brightgreen) • ![MCP Tools](https://img.shields.io/badge/MCP_Tools-20-blue) • [![GitHub issues](https://img.shields.io/github/issues/NLR-Distribution-Suite/grid-data-models)](https://github.com/NLR-Distribution-Suite/grid-data-models/issues) • [![License](https://img.shields.io/github/license/NLR-Distribution-Suite/grid-data-models)](https://github.com/NLR-Distribution-Suite/grid-data-models/blob/main/LICENSE.txt) • [![PyPI Downloads](https://static.pepy.tech/personalized-badge/grid-data-models?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/grid-data-models) • [![Generate LinkedIn Post Draft](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/linkedin_draft.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/linkedin_draft.yml)
45
45
 
46
46
  # Grid Data Models (GDM)
47
47
 
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- [![Upload to PyPi](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/publish_to_pypi.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/publish_to_pypi.yml) • [![Pytest](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/pull_request_tests.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/pull_request_tests.yml) • [![deploy-book](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/deploy.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/deploy.yml) • ![PyPI - Downloads](https://img.shields.io/pypi/dm/grid-data-models) • [![codecov](https://codecov.io/github/NLR-Distribution-Suite/grid-data-models/branch/main/graph/badge.svg?token=K0X11EXOX8)](https://codecov.io/github/NLR-Distribution-Suite/grid-data-models) • [![CodeFactor](https://www.codefactor.io/repository/github/nlr-distribution-suite/grid-data-models/badge)](https://www.codefactor.io/repository/github/nlr-distribution-suite/grid-data-models) • ![MCP Server](https://img.shields.io/badge/MCP_Server-enabled-brightgreen) • ![MCP Tools](https://img.shields.io/badge/MCP_Tools-20-blue) • [![GitHub issues](https://img.shields.io/github/issues/NLR-Distribution-Suite/grid-data-models)](https://github.com/NLR-Distribution-Suite/grid-data-models/issues) • [![License](https://img.shields.io/github/license/NLR-Distribution-Suite/grid-data-models)](https://github.com/NLR-Distribution-Suite/grid-data-models/blob/main/LICENSE.txt) • [![PyPI Downloads](https://static.pepy.tech/personalized-badge/grid-data-models?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/grid-data-models)
3
+ [![Upload to PyPi](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/publish_to_pypi.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/publish_to_pypi.yml) • [![Pytest](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/pull_request_tests.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/pull_request_tests.yml) • [![deploy-book](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/deploy.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/deploy.yml) • ![PyPI - Downloads](https://img.shields.io/pypi/dm/grid-data-models) • [![codecov](https://codecov.io/github/NLR-Distribution-Suite/grid-data-models/branch/main/graph/badge.svg?token=K0X11EXOX8)](https://codecov.io/github/NLR-Distribution-Suite/grid-data-models) • [![CodeFactor](https://www.codefactor.io/repository/github/nlr-distribution-suite/grid-data-models/badge)](https://www.codefactor.io/repository/github/nlr-distribution-suite/grid-data-models) • ![MCP Server](https://img.shields.io/badge/MCP_Server-enabled-brightgreen) • ![MCP Tools](https://img.shields.io/badge/MCP_Tools-20-blue) • [![GitHub issues](https://img.shields.io/github/issues/NLR-Distribution-Suite/grid-data-models)](https://github.com/NLR-Distribution-Suite/grid-data-models/issues) • [![License](https://img.shields.io/github/license/NLR-Distribution-Suite/grid-data-models)](https://github.com/NLR-Distribution-Suite/grid-data-models/blob/main/LICENSE.txt) • [![PyPI Downloads](https://static.pepy.tech/personalized-badge/grid-data-models?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/grid-data-models) • [![Generate LinkedIn Post Draft](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/linkedin_draft.yml/badge.svg)](https://github.com/NLR-Distribution-Suite/grid-data-models/actions/workflows/linkedin_draft.yml)
4
4
 
5
5
  # Grid Data Models (GDM)
6
6
 
@@ -33,7 +33,7 @@ dependencies = [
33
33
  ]
34
34
 
35
35
  [project.optional-dependencies]
36
- mcp = ["mcp>=1.27.2"]
36
+ mcp = ["mcp>=1.28.0"]
37
37
  dev = ["pre-commit", "pytest", "pytest-cov", "pytest-doctestplus", "pytest-asyncio", "ruff", "docutils"]
38
38
  doc = [
39
39
  "jupyter-book>=2.1.5,<3",
@@ -7,6 +7,7 @@ from gdm.distribution.components.distribution_solar import DistributionSolar
7
7
  from gdm.distribution.components.distribution_transformer import DistributionTransformer
8
8
  from gdm.distribution.components.distribution_vsource import DistributionVoltageSource
9
9
  from gdm.distribution.components.geometry_branch import GeometryBranch
10
+ from gdm.distribution.components.geometry_switch import GeometrySwitch
10
11
  from gdm.distribution.components.matrix_impedance_branch import MatrixImpedanceBranch
11
12
  from gdm.distribution.components.matrix_impedance_fuse import MatrixImpedanceFuse
12
13
  from gdm.distribution.components.matrix_impedance_recloser import MatrixImpedanceRecloser
@@ -44,6 +44,14 @@ class DistributionTransformerBase(InServiceDistributionComponentBase, ABC):
44
44
  order defined in the DistributionTransformerModel""",
45
45
  ),
46
46
  ]
47
+ tap_positions: Annotated[
48
+ list[list[float]] | None,
49
+ Field(
50
+ default=None,
51
+ description="Current tap position (per-unit) for each phase in each winding, "
52
+ "matching the order of winding_phases. Defaults to 1.0 for all phases.",
53
+ ),
54
+ ]
47
55
 
48
56
  equipment: Annotated[
49
57
  DistributionTransformerEquipment,
@@ -67,6 +75,26 @@ class DistributionTransformerBase(InServiceDistributionComponentBase, ABC):
67
75
  "Center tapped transformer must have same bus on the secondary side (bus 2 and 3)."
68
76
  )
69
77
 
78
+ def _validate_tap_positions(self) -> None:
79
+ """Validate and default tap_positions field."""
80
+ if self.tap_positions is None:
81
+ self.tap_positions = [[1.0] * len(phases) for phases in self.winding_phases]
82
+
83
+ if len(self.tap_positions) != len(self.winding_phases):
84
+ msg = (
85
+ f"Number of tap_positions entries {len(self.tap_positions)} must equal "
86
+ f"number of windings {len(self.winding_phases)}"
87
+ )
88
+ raise ValueError(msg)
89
+
90
+ for i, (taps, phases) in enumerate(zip(self.tap_positions, self.winding_phases)):
91
+ if len(taps) != len(phases):
92
+ msg = (
93
+ f"tap_positions[{i}] has {len(taps)} entries but "
94
+ f"winding_phases[{i}] has {len(phases)} phases"
95
+ )
96
+ raise ValueError(msg)
97
+
70
98
  @model_validator(mode="after")
71
99
  def validate_fields_base(self) -> "DistributionTransformerBase":
72
100
  """Custom validator for distribution transformer."""
@@ -77,6 +105,8 @@ class DistributionTransformerBase(InServiceDistributionComponentBase, ABC):
77
105
  )
78
106
  raise ValueError(msg)
79
107
 
108
+ self._validate_tap_positions()
109
+
80
110
  for wdg, pw_phases in zip(self.equipment.windings, self.winding_phases):
81
111
  pw_phases_length = len(pw_phases) - 1 if Phase.N in pw_phases else len(pw_phases)
82
112
 
@@ -3,7 +3,7 @@
3
3
  from collections import defaultdict
4
4
  from typing import Annotated
5
5
 
6
- from pydantic import Field, model_validator
6
+ from pydantic import Field, computed_field, model_validator
7
7
 
8
8
  from gdm.distribution.components.distribution_bus import DistributionBus
9
9
  from gdm.distribution.equipment.capacitor_equipment import CapacitorEquipment
@@ -49,6 +49,19 @@ class DistributionCapacitor(InServiceDistributionComponentBase):
49
49
  ]
50
50
 
51
51
  equipment: Annotated[CapacitorEquipment, Field(..., description="Capacitor model.")]
52
+ state: Annotated[
53
+ list[bool],
54
+ Field(
55
+ ...,
56
+ description="List of boolean states indicating whether each bank is on (True) or off (False).",
57
+ ),
58
+ ]
59
+
60
+ @computed_field # type: ignore[prop-decorator]
61
+ @property
62
+ def num_banks(self) -> int:
63
+ """Number of banks in the capacitor."""
64
+ return len(self.state)
52
65
 
53
66
  @classmethod
54
67
  def aggregate(
@@ -86,6 +99,7 @@ class DistributionCapacitor(InServiceDistributionComponentBase):
86
99
  rated_voltage=bus.rated_voltage,
87
100
  voltage_type=bus.voltage_type,
88
101
  ),
102
+ state=[True] * len(phase_caps),
89
103
  )
90
104
 
91
105
  @model_validator(mode="after")
@@ -126,9 +140,8 @@ class DistributionCapacitor(InServiceDistributionComponentBase):
126
140
  feeder=DistributionFeeder.example(),
127
141
  phases=[Phase.A, Phase.B, Phase.C],
128
142
  equipment=CapacitorEquipment.example(),
143
+ state=[True, True, True],
129
144
  controllers=[
130
145
  VoltageCapacitorController.example(),
131
- VoltageCapacitorController.example(),
132
- VoltageCapacitorController.example(),
133
146
  ],
134
147
  )
@@ -63,6 +63,7 @@ class DistributionRegulator(DistributionTransformerBase):
63
63
  ),
64
64
  ],
65
65
  winding_phases=[[Phase.A, Phase.B, Phase.C], [Phase.A, Phase.B, Phase.C]],
66
+ tap_positions=[[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]],
66
67
  equipment=DistributionTransformerEquipment.example(),
67
68
  controllers=[
68
69
  RegulatorController.example(),
@@ -42,5 +42,6 @@ class DistributionTransformer(DistributionTransformerBase):
42
42
  substation=DistributionSubstation.example(),
43
43
  feeder=DistributionFeeder.example(),
44
44
  winding_phases=[[Phase.A, Phase.B, Phase.C], [Phase.A, Phase.B, Phase.C]],
45
+ tap_positions=[[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]],
45
46
  equipment=DistributionTransformerEquipment.example(),
46
47
  )
@@ -0,0 +1,122 @@
1
+ """This module contains geometry branch."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import Field
6
+
7
+ from gdm.distribution.components.base.distribution_switch_base import (
8
+ DistributionSwitchBase,
9
+ )
10
+ from gdm.distribution.equipment.geometry_branch_equipment import (
11
+ GeometryBranchEquipment,
12
+ )
13
+ from gdm.distribution.components.distribution_feeder import DistributionFeeder
14
+ from gdm.distribution.components.distribution_substation import (
15
+ DistributionSubstation,
16
+ )
17
+ from gdm.distribution.components.distribution_bus import DistributionBus
18
+ from gdm.distribution.enums import Phase
19
+ from gdm.quantities import Voltage, Distance
20
+
21
+ from gdm.distribution.components.matrix_impedance_switch import MatrixImpedanceSwitch
22
+ from gdm.distribution.equipment import (
23
+ ConcentricCableEquipment,
24
+ )
25
+ from gdm.distribution.equipment.matrix_impedance_switch_equipment import (
26
+ MatrixImpedanceSwitchEquipment,
27
+ )
28
+
29
+
30
+ class GeometrySwitch(DistributionSwitchBase):
31
+ """Data model for distribution branches based on line geometry."""
32
+
33
+ equipment: Annotated[
34
+ GeometryBranchEquipment,
35
+ Field(..., description="Geometry branch equipment."),
36
+ ]
37
+
38
+ def validate_fields(self) -> "GeometrySwitch":
39
+ """Custom validator for geometry branch fields."""
40
+ if len(self.phases) != len(self.equipment.conductors):
41
+ msg = "Number of phases is not equal to number of wires."
42
+ raise ValueError(msg)
43
+ return self
44
+
45
+ def to_matrix_representation(
46
+ self, frequency_hz: float = 60, soil_resistivity_ohm_m: float = 100
47
+ ) -> MatrixImpedanceSwitch:
48
+ """
49
+ Converts the geometry-based branch model to a matrix impedance representation.
50
+
51
+ This method transforms the current `GeometryBranch` instance into a `MatrixImpedanceBranch`
52
+ by calculating the impedance matrix based on the specified frequency and soil resistivity.
53
+ The conversion process involves using the equipment's matrix representation method to obtain
54
+ the necessary impedance data.
55
+
56
+ Parameters
57
+ ----------
58
+ frequency_hz : float, optional
59
+ The frequency in hertz used for impedance calculations. Defaults to 60 Hz.
60
+ soil_resistivity_ohm_m : float, optional
61
+ The soil resistivity in ohm-meters used for impedance calculations. Defaults to 100 ohm-m.
62
+
63
+ Returns
64
+ -------
65
+ MatrixImpedanceBranch
66
+ A new instance of `MatrixImpedanceBranch` representing the converted geometry branch.
67
+
68
+ Notes
69
+ -----
70
+ - The method uses the number of neutral phases to assist in the impedance calculation.
71
+ - This conversion is essential for systems that require matrix impedance representations
72
+ for detailed electrical analysis.
73
+
74
+ """
75
+ equipment = self.equipment.to_matrix_representation(
76
+ frequency_hz,
77
+ soil_resistivity_ohm_m,
78
+ n_neutrals=len([phs for phs in self.phases if phs == Phase.N]),
79
+ )
80
+ if isinstance(self.equipment.conductors[0], ConcentricCableEquipment):
81
+ phases = self.phases + [Phase.N for _ in self.phases]
82
+ equipment.kron_reduce(phases)
83
+
84
+ switch_equipment = MatrixImpedanceSwitchEquipment(
85
+ **equipment.model_dump(exclude_none=True)
86
+ )
87
+
88
+ return MatrixImpedanceSwitch(
89
+ buses=self.buses,
90
+ length=self.length,
91
+ phases=self.phases,
92
+ substation=self.substation,
93
+ feeder=self.feeder,
94
+ name=self.name,
95
+ equipment=switch_equipment,
96
+ is_closed=self.is_closed,
97
+ )
98
+
99
+ @classmethod
100
+ def example(cls) -> "GeometrySwitch":
101
+ """Example for geometry branch."""
102
+ buses = [
103
+ DistributionBus(
104
+ voltage_type="line-to-ground",
105
+ phases=[Phase.A, Phase.B, Phase.C],
106
+ rated_voltage=Voltage(400, "volt"),
107
+ substation=DistributionSubstation.example(),
108
+ feeder=DistributionFeeder.example(),
109
+ name=bus_name,
110
+ )
111
+ for bus_name in ["Branch-DistBus1", "Branch-DistBus2"]
112
+ ]
113
+ return GeometrySwitch(
114
+ buses=buses,
115
+ length=Distance(130.2, "meter"),
116
+ phases=[Phase.A, Phase.B, Phase.C],
117
+ substation=DistributionSubstation.example(),
118
+ feeder=DistributionFeeder.example(),
119
+ name="DistBranch1",
120
+ is_closed=[True, True, True],
121
+ equipment=GeometryBranchEquipment.example(),
122
+ )
@@ -7,6 +7,7 @@ from pathlib import Path
7
7
  import tempfile
8
8
  import random
9
9
 
10
+ from gdm.distribution.components.geometry_switch import GeometrySwitch
10
11
  from infrasys.time_series_models import TimeSeriesData, SingleTimeSeries
11
12
  from shapely import Point, LineString, union_all
12
13
  from infrasys import Component, System
@@ -951,7 +952,7 @@ class DistributionSystem(System):
951
952
  logger.info("Converting models from GeometryBranch to MatrixImpedanceBranch...")
952
953
  auto_add = self.auto_add_composed_components
953
954
  self.auto_add_composed_components = True
954
- branches = list(self.get_components(GeometryBranch))
955
+ branches = list(self.get_components(GeometryBranch)) + list(self.get_components(GeometrySwitch))
955
956
  for branch in branches:
956
957
  impedence_branch = branch.to_matrix_representation(
957
958
  frequency_hz, soil_resistivity_ohm_m
@@ -0,0 +1,15 @@
1
+ from loguru import logger
2
+ from infrasys.migrations.metadata_migration import migrate_component_metadata
3
+
4
+
5
+ def from__2_3_4__to__2_3_5(data: dict, from_version: str, to_version: str) -> dict:
6
+ logger.info(f"Upgrading DistributionSystem from verion {from_version} to {to_version}")
7
+ data["data_format_version"] = str(to_version)
8
+ number_of_components_before = len(data["components"])
9
+ data["components"] = migrate_component_metadata(data["components"])
10
+ number_of_components_after = len(data["components"])
11
+ assert (
12
+ number_of_components_before == number_of_components_after
13
+ ), "Number of components should be the same before and after model upgrade"
14
+
15
+ return data
@@ -0,0 +1,70 @@
1
+ from loguru import logger
2
+ from infrasys.migrations.metadata_migration import migrate_component_metadata
3
+
4
+
5
+ TRANSFORMER_TYPES = {"DistributionTransformer", "DistributionRegulator"}
6
+ CAPACITOR_TYPE = "DistributionCapacitor"
7
+
8
+
9
+ def _get_component_type(component: dict) -> str | None:
10
+ metadata = component.get("__metadata__", {})
11
+ if "fields" in metadata:
12
+ return metadata["fields"].get("type")
13
+ return metadata.get("type")
14
+
15
+
16
+ def _add_tap_positions(obj: dict) -> None:
17
+ """Add tap_positions field to transformer-based components if missing."""
18
+ component_type = _get_component_type(obj)
19
+ if component_type in TRANSFORMER_TYPES:
20
+ if "tap_positions" not in obj:
21
+ winding_phases = obj.get("winding_phases", [])
22
+ obj["tap_positions"] = [[1.0] * len(phases) for phases in winding_phases]
23
+ logger.debug(f"Migrated {component_type}: added default tap_positions")
24
+
25
+ # Recurse into nested dicts/lists
26
+ for value in obj.values():
27
+ if isinstance(value, dict):
28
+ _add_tap_positions(value)
29
+ elif isinstance(value, list):
30
+ for item in value:
31
+ if isinstance(item, dict):
32
+ _add_tap_positions(item)
33
+
34
+
35
+ def _add_capacitor_state(obj: dict) -> None:
36
+ """Add state field to DistributionCapacitor components if missing."""
37
+ component_type = _get_component_type(obj)
38
+ if component_type == CAPACITOR_TYPE:
39
+ if "state" not in obj:
40
+ # Default: all banks on, one per phase
41
+ phases = obj.get("phases", [])
42
+ obj["state"] = [True] * len(phases)
43
+ logger.debug("Migrated DistributionCapacitor: added default state (all True)")
44
+
45
+ # Recurse into nested dicts/lists
46
+ for value in obj.values():
47
+ if isinstance(value, dict):
48
+ _add_capacitor_state(value)
49
+ elif isinstance(value, list):
50
+ for item in value:
51
+ if isinstance(item, dict):
52
+ _add_capacitor_state(item)
53
+
54
+
55
+ def from__2_3_5__to__2_3_6(data: dict, from_version: str, to_version: str) -> dict:
56
+ logger.info(f"Upgrading DistributionSystem from verion {from_version} to {to_version}")
57
+ data["data_format_version"] = str(to_version)
58
+ number_of_components_before = len(data["components"])
59
+
60
+ for component in data["components"]:
61
+ _add_tap_positions(component)
62
+ _add_capacitor_state(component)
63
+
64
+ data["components"] = migrate_component_metadata(data["components"])
65
+ number_of_components_after = len(data["components"])
66
+ assert (
67
+ number_of_components_before == number_of_components_after
68
+ ), "Number of components should be the same before and after model upgrade"
69
+
70
+ return data
@@ -22,6 +22,8 @@ from gdm.distribution.upgrade_handler.from__2_3_0__to__2_3_1 import from__2_3_0_
22
22
  from gdm.distribution.upgrade_handler.from__2_3_1__to__2_3_2 import from__2_3_1__to__2_3_2
23
23
  from gdm.distribution.upgrade_handler.from__2_3_2__to__2_3_3 import from__2_3_2__to__2_3_3
24
24
  from gdm.distribution.upgrade_handler.from__2_3_3__to__2_3_4 import from__2_3_3__to__2_3_4
25
+ from gdm.distribution.upgrade_handler.from__2_3_4__to__2_3_5 import from__2_3_4__to__2_3_5
26
+ from gdm.distribution.upgrade_handler.from__2_3_5__to__2_3_6 import from__2_3_5__to__2_3_6
25
27
 
26
28
 
27
29
  def fix_version(version):
@@ -124,6 +126,16 @@ class UpgradeHandler(BaseModel):
124
126
  from_version="2.3.3",
125
127
  to_version="2.3.4",
126
128
  ),
129
+ UpgradeSchema(
130
+ method=from__2_3_4__to__2_3_5,
131
+ from_version="2.3.4",
132
+ to_version="2.3.5",
133
+ ),
134
+ UpgradeSchema(
135
+ method=from__2_3_5__to__2_3_6,
136
+ from_version="2.3.5",
137
+ to_version="2.3.6",
138
+ ),
127
139
  ]
128
140
 
129
141
  @model_validator(mode="after")
@@ -0,0 +1,5 @@
1
+ """Distribution utility functions."""
2
+
3
+ from gdm.distribution.utils.aggregate_components import aggregate_single_phase_transformers
4
+
5
+ __all__ = ["aggregate_single_phase_transformers"]
@@ -0,0 +1,181 @@
1
+ """Utility to aggregate parallel single-phase transformer-based components into three-phase."""
2
+
3
+ from collections import defaultdict
4
+ from typing import TypeVar
5
+
6
+ from gdm.distribution import DistributionSystem
7
+ from gdm.distribution.components.base.distribution_transformer_base import (
8
+ DistributionTransformerBase,
9
+ )
10
+ from gdm.distribution.components.distribution_transformer import DistributionTransformer
11
+ from gdm.distribution.components.distribution_regulator import DistributionRegulator
12
+ from gdm.distribution.equipment.distribution_transformer_equipment import (
13
+ DistributionTransformerEquipment,
14
+ WindingEquipment,
15
+ )
16
+
17
+ T = TypeVar("T", bound=DistributionTransformerBase)
18
+
19
+
20
+ def _bus_key(component: DistributionTransformerBase) -> tuple[str, ...]:
21
+ """Return a hashable key of bus names for grouping parallel components."""
22
+ return tuple(bus.name for bus in component.buses)
23
+
24
+
25
+ def _is_single_phase(component: DistributionTransformerBase) -> bool:
26
+ """Check if all windings are single-phase."""
27
+ return all(wdg.num_phases == 1 for wdg in component.equipment.windings)
28
+
29
+
30
+ def _merge_windings(components: list[DistributionTransformerBase]) -> list[WindingEquipment]:
31
+ """Merge single-phase windings into three-phase windings.
32
+
33
+ Assumes all components have the same number of windings and compatible equipment.
34
+ Rated power is summed; resistance and tap bounds are averaged.
35
+ """
36
+ num_windings = len(components[0].equipment.windings)
37
+ merged = []
38
+ for wdg_idx in range(num_windings):
39
+ ref_wdg = components[0].equipment.windings[wdg_idx]
40
+ total_power = sum(comp.equipment.windings[wdg_idx].rated_power for comp in components)
41
+ avg_resistance = sum(
42
+ comp.equipment.windings[wdg_idx].resistance for comp in components
43
+ ) / len(components)
44
+ all_taps = [comp.equipment.windings[wdg_idx].tap_positions[0] for comp in components]
45
+ merged.append(
46
+ WindingEquipment(
47
+ name=ref_wdg.name,
48
+ resistance=avg_resistance,
49
+ is_grounded=ref_wdg.is_grounded,
50
+ rated_voltage=ref_wdg.rated_voltage,
51
+ voltage_type=ref_wdg.voltage_type,
52
+ rated_power=total_power,
53
+ num_phases=len(components),
54
+ connection_type=ref_wdg.connection_type,
55
+ tap_positions=all_taps,
56
+ total_taps=ref_wdg.total_taps,
57
+ min_tap_pu=ref_wdg.min_tap_pu,
58
+ max_tap_pu=ref_wdg.max_tap_pu,
59
+ )
60
+ )
61
+ return merged
62
+
63
+
64
+ def _merge_group(components: list[T]) -> T:
65
+ """Merge a group of parallel single-phase components into one three-phase component.
66
+
67
+ Parameters
68
+ ----------
69
+ components : list[T]
70
+ List of single-phase components connected to the same buses.
71
+
72
+ Returns
73
+ -------
74
+ T
75
+ A single three-phase component of the same type.
76
+ """
77
+ ref = components[0]
78
+ component_type = type(ref)
79
+
80
+ merged_windings = _merge_windings(components)
81
+
82
+ # Build merged winding_phases and tap_positions from each component's per-winding phases
83
+ merged_winding_phases = []
84
+ merged_tap_positions = []
85
+ for wdg_idx in range(len(ref.equipment.windings)):
86
+ phases = []
87
+ taps = []
88
+ for comp in components:
89
+ phases.extend(comp.winding_phases[wdg_idx])
90
+ if comp.tap_positions is not None:
91
+ taps.extend(comp.tap_positions[wdg_idx])
92
+ else:
93
+ taps.extend([1.0] * len(comp.winding_phases[wdg_idx]))
94
+ merged_winding_phases.append(phases)
95
+ merged_tap_positions.append(taps)
96
+
97
+ merged_equipment = DistributionTransformerEquipment(
98
+ name=f"{ref.equipment.name}_merged",
99
+ mounting=ref.equipment.mounting,
100
+ pct_no_load_loss=ref.equipment.pct_no_load_loss,
101
+ pct_full_load_loss=ref.equipment.pct_full_load_loss,
102
+ windings=merged_windings,
103
+ coupling_sequences=ref.equipment.coupling_sequences,
104
+ winding_reactances=ref.equipment.winding_reactances,
105
+ is_center_tapped=False,
106
+ )
107
+
108
+ kwargs = {
109
+ "name": f"{ref.name}_merged",
110
+ "buses": ref.buses,
111
+ "winding_phases": merged_winding_phases,
112
+ "tap_positions": merged_tap_positions,
113
+ "equipment": merged_equipment,
114
+ }
115
+
116
+ # Preserve substation/feeder if present
117
+ if hasattr(ref, "substation") and ref.substation is not None:
118
+ kwargs["substation"] = ref.substation
119
+ if hasattr(ref, "feeder") and ref.feeder is not None:
120
+ kwargs["feeder"] = ref.feeder
121
+
122
+ # For regulators, merge controllers
123
+ if component_type is DistributionRegulator:
124
+ controllers = []
125
+ for comp in components:
126
+ controllers.extend(comp.controllers)
127
+ kwargs["controllers"] = controllers
128
+
129
+ return component_type(**kwargs)
130
+
131
+
132
+ def aggregate_single_phase_transformers(
133
+ system: DistributionSystem,
134
+ ) -> DistributionSystem:
135
+ """Replace groups of parallel single-phase transformer-based components with
136
+ a single three-phase equivalent.
137
+
138
+ Finds all DistributionTransformer and DistributionRegulator components that are:
139
+ - Single-phase (all windings have num_phases == 1)
140
+ - Connected to the same set of buses
141
+
142
+ Groups of 3 such components are merged into one three-phase component.
143
+ The original single-phase components are removed and the merged component is added.
144
+
145
+ Parameters
146
+ ----------
147
+ system : DistributionSystem
148
+ Input distribution system (will be modified in place).
149
+
150
+ Returns
151
+ -------
152
+ DistributionSystem
153
+ The same system reference, modified with merged components.
154
+ """
155
+ for component_type in (DistributionTransformer, DistributionRegulator):
156
+ # Group single-phase components by their bus connections
157
+ groups: dict[tuple[str, ...], list] = defaultdict(list)
158
+ for comp in system.get_components(component_type):
159
+ if _is_single_phase(comp):
160
+ groups[_bus_key(comp)].append(comp)
161
+
162
+ # Merge groups of exactly 3 parallel single-phase components
163
+ for key, group in groups.items():
164
+ if len(group) != 3:
165
+ continue
166
+
167
+ merged = _merge_group(group)
168
+
169
+ # Remove originals and add merged.
170
+ # cascade_down=False because grouped components share children (buses, equipment)
171
+ # that the merged component still references.
172
+ for comp in group:
173
+ system.remove_component(comp, cascade_down=False)
174
+
175
+ # Add composed children (windings, equipment) before the merged component
176
+ for winding in merged.equipment.windings:
177
+ system.add_component(winding)
178
+ system.add_component(merged.equipment)
179
+ system.add_component(merged)
180
+
181
+ return system
@@ -3,7 +3,7 @@ import subprocess
3
3
  import platform
4
4
  import sys
5
5
 
6
- VERSION = "2.3.4"
6
+ VERSION = "2.3.6"
7
7
  SUPPORTED_VERSIONS = [VERSION]
8
8
 
9
9