Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
43457ba
.
chestm007 May 18, 2025
e007fb4
more tests, slight optimisation
chestm007 May 18, 2025
dc7870a
Add step path provider, and some tests
chestm007 May 19, 2025
7c4b033
be helpful if the TestNetworkBuilder knew what a Busbar was
chestm007 May 19, 2025
3b69b62
stashing changes
chestm007 May 20, 2025
9b9ce0d
NetworkTraceStepPathProvider seems to do the needful
chestm007 May 20, 2025
3777cde
Network Trace now supports cuts and clamps
chestm007 May 20, 2025
5a6f2d8
Reworking some internal classes to be less boilerplate
chestm007 May 21, 2025
847539e
this class isnt a test...
chestm007 May 21, 2025
a972967
Reworked NetworkStateOperators so auto complete works
chestm007 May 21, 2025
44e083d
I broke some type hinting, and created circular imports. #winning
chestm007 May 21, 2025
cf24250
Pretty bulky change
chestm007 May 21, 2025
a410f72
Reworked the internals of network_trace.Conditions
chestm007 May 22, 2025
0a3a303
.
chestm007 May 22, 2025
e31b95e
get rid of alot of `if isinstance` in favor of singledispatch
chestm007 May 23, 2025
9589cd3
Fixed WeightedPriorityQueue, and rework of network trace/traversal
chestm007 May 24, 2025
fb08464
More typing, and refactoring code to be more pythonic
chestm007 May 24, 2025
81d7f7f
slight refactoring to clean up API
chestm007 May 24, 2025
06c7d4e
Chasing an exponential queueing issue
chestm007 May 26, 2025
6c54dbf
yield != return.
chestm007 May 26, 2025
8b4f434
codebase inline with ewb-sdk-jvm PR#220
chestm007 May 26, 2025
519c0b9
DEV-2568 PR#220, tracing fixes, and more unit tests - 2 unexpected te…
chestm007 May 26, 2025
c80c9be
because ofcourse i accidentally wrote tests that modify global state
chestm007 May 27, 2025
66d0449
pointless code prettifying
chestm007 May 27, 2025
4947e4b
comparing a bound method to an enum will always return False silently
chestm007 May 27, 2025
d4d9d17
[DEV-2573] changes ported from Kotlin SDK (#221)
chestm007 May 27, 2025
84453a9
Busbranch tests fixed, this needs verification that its not dumb
chestm007 May 27, 2025
2510f21
phase inferring is solved 💪
chestm007 May 27, 2025
3c17140
Added action blocks for `TestNetworkBuilder.with_clamp` and `TestNetw…
chestm007 May 27, 2025
bfd7add
[DEV-2592] - added tests from kotlin sdk
chestm007 May 27, 2025
abff444
[DEV-2766] `NetworkTrace`/`Traversal` now correctly respects `can_sto…
chestm007 May 27, 2025
242c10b
rebase off main incase new build system takes a while
chestm007 May 27, 2025
5ebd932
remove version lock for pytest-subtests, it can sort itself out
chestm007 May 27, 2025
5c543ae
DEV-2382
chestm007 May 27, 2025
9dbfb65
should update the changelog.
chestm007 May 27, 2025
e7781b3
log inferred phases properly.
chestm007 May 28, 2025
18e06d5
small changes adressing some PR comments, more left to address.
chestm007 May 30, 2025
bef32b9
dont keep overwritten class methods after the test scope
chestm007 May 30, 2025
b30bd9c
added definitely missed changelog notes
chestm007 May 30, 2025
db71519
added __all__ where missing to affected imports, also added missing i…
chestm007 May 30, 2025
e2d09d4
Another one
chestm007 May 30, 2025
0636f5c
Type hints, formatting, and tidyups requested
chestm007 May 30, 2025
d85b342
rework of NetworkTraceStepPathProvider while i was there fixing a log…
chestm007 May 30, 2025
08c09c1
.
chestm007 May 30, 2025
a23ac3e
add default_mrid_prefix to TestNetworkBuilder
chestm007 May 30, 2025
a5467c6
Type hint fixes
chestm007 Jun 3, 2025
803697f
Fixed terminal test.
charlta Jun 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,5 @@ dmypy.json

/todo.md
test/resources/test-network-database.txt

*.iml
36 changes: 33 additions & 3 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,45 @@
## [0.48.0] - UNRELEASED
### Breaking Changes
* Updated to new Tracing API. All old traces will need to be re-written with the new API.
* `AcLineSegment` supports adding a maximum of 2 terminals. Mid-span terminals are no longer supported and models should migrate to using `Clamp`.
* `Clamp` supports only adding a single terminal.
* `FeederDirectionStateOperations` have been reworked to take `NetworkStateOperators` as a parameter.
* `RemoveDirection` has been removed. It did not work reliably with dual fed networks with loops. You now need to clear direction using the new
`ClearDirection` and reapply directions where appropriate using `SetDirection`.
* `Cut` supports adding a maximum of 2 terminals.


### New Features
* None.
* Added `ClearDirection` that clears feeder directions.

### Enhancements
* None.
* Tracing models with `Cut` and `Clamp` are now supported via the new tracing API.
* Added support to `TestNetworkBuilder` for:
* `with_clamp` - Adds a clamp to the previously added `AcLineSegment`
* `with_cut` - Adds a cut to the previously added `AcLineSegment`
* `connect_to` - Connects the previously added item, rather than having to specify it again in `connect`.
* You can now add sites to the `TestNetworkBuilder` via `addSite`.
* You can now add busbar sections natively with `from_busbar_section` and `to_busbar_section`
* The prefix for generated mRIDs for "other" equipment can be specified with the `default_mrid_prefix` argument in `from_other` and `to_other`.
* When processing feeder assignments, all LV feeders belonging to a dist substation site will now be considered energized when the site is energized by a feeder.



### Fixes
When finding `LvFeeders` in the `Site` we will now exclude `LvFeeders` that start with an open `Switch`
* When finding `LvFeeders` in the `Site` we will now exclude `LvFeeders` that start with an open `Switch`
* `AssignToFeeder` and `AssignToLvFeeder` will no longer trace from start terminals that belong to open switches
* The follow fixes were added to Traversal and NetworkTrace:
* `can_stop_on_start_item` now works for branching traversals.
* Traversal start items are added to the queue before traversal starts, so that the start items honour the queue type order.
* Stop conditions on the `NetworkTrace` now are checked based on a step type, like `QueueCondition` does, rather than by checking `can_action_item`.
* `Cut` and `Clamp` are now correctly supported in `SetDirection` and `DirectionCondition`.
* `NetworkTrace` now handles starting on `Cut` , `Clamp`, and `AcLineSegment` and their terminals in a explicit / sensible way.
* `NetworkTracePathProvider` now correctly handles next paths when starting on a `Clamp` terminal.
* `NetworkTrace`/`Traversal` now correctly respects `can_stop_on_start_item` when providing multiple start items.
* `AssignToFeeders`/`AssignToLvFeeders` now finds back-fed equipment correctly
* `AssignToFeeders` and `AssignToLvFeeders` will now associate `PowerElectronicUnits` with their `powerElectronicsConnection` `Feeder`/`LvFeeder`.



### Notes
* None.
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"pytest-cov==2.10.1",
"pytest-asyncio==0.19.0",
"pytest-timeout==1.4.2",
'pytest-subtests',
"hypothesis==6.56.3",
"grpcio-testing==1.61.3",
"pylint==2.14.5",
Expand Down
44 changes: 35 additions & 9 deletions src/zepben/evolve/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,7 @@
from zepben.evolve.model.phases import *
from zepben.evolve.model.resistance_reactance import *

from zepben.evolve.services.network.tracing.traversal.traversal import *
from zepben.evolve.services.network.tracing.traversal.queue import *

from zepben.evolve.services.network.tracing.feeder.feeder_direction import *
from zepben.evolve.services.network.tracing.util import *

from zepben.evolve.services.network.translator.network_proto2cim import *
Expand All @@ -167,17 +164,47 @@
from zepben.evolve.services.network.tracing.connectivity.transformer_phase_paths import *
from zepben.evolve.services.network.tracing.connectivity.xy_candidate_phase_paths import *
from zepben.evolve.services.network.tracing.connectivity.xy_phase_step import *
from zepben.evolve.services.network.tracing.feeder.direction_status import *

from zepben.evolve.services.network.tracing.feeder.assign_to_feeders import *
from zepben.evolve.services.network.tracing.feeder.assign_to_lv_feeders import *
from zepben.evolve.services.network.tracing.feeder.clear_direction import *
from zepben.evolve.services.network.tracing.feeder.direction_status import *
from zepben.evolve.services.network.tracing.feeder.feeder_direction import *
from zepben.evolve.services.network.tracing.feeder.set_direction import *

from zepben.evolve.services.network.tracing.networktrace.actions.equipment_tree_builder import *
from zepben.evolve.services.network.tracing.networktrace.actions.tree_node import *
from zepben.evolve.services.network.tracing.networktrace.conditions.conditions import *
from zepben.evolve.services.network.tracing.networktrace.conditions.direction_condition import *
from zepben.evolve.services.network.tracing.networktrace.conditions.equipment_step_limit_condition import *
from zepben.evolve.services.network.tracing.networktrace.conditions.equipment_type_step_limit_condition import *
from zepben.evolve.services.network.tracing.networktrace.conditions.network_trace_stop_condition import *
from zepben.evolve.services.network.tracing.networktrace.conditions.network_trace_queue_condition import *
from zepben.evolve.services.network.tracing.networktrace.conditions.open_condition import *
from zepben.evolve.services.network.tracing.networktrace.operators.equipment_container_state_operators import *
from zepben.evolve.services.network.tracing.networktrace.operators.feeder_direction_state_operations import *
from zepben.evolve.services.network.tracing.networktrace.operators.in_service_state_operators import *
from zepben.evolve.services.network.tracing.networktrace.operators.network_state_operators import *
from zepben.evolve.services.network.tracing.networktrace.operators.open_state_operators import *
from zepben.evolve.services.network.tracing.networktrace.operators.phase_state_operators import *
from zepben.evolve.services.network.tracing.networktrace.compute_data import *

from zepben.evolve.services.network.tracing.phases.phase_status import *
from zepben.evolve.services.network.tracing.phases.phase_inferrer import *
from zepben.evolve.services.network.tracing.phases.remove_phases import *
from zepben.evolve.services.network.tracing.find_swer_equipment import *
from zepben.evolve.services.network.tracing.traversal.queue_condition import *
from zepben.evolve.services.network.tracing.phases.set_phases import *

from zepben.evolve.services.network.tracing.traversal.context_value_computer import *
from zepben.evolve.services.network.tracing.traversal.step_action import StepAction
from zepben.evolve.services.network.tracing.feeder.set_direction import *
from zepben.evolve.services.network.tracing.traversal.queue import *
from zepben.evolve.services.network.tracing.traversal.queue_condition import *
from zepben.evolve.services.network.tracing.traversal.step_action import *
from zepben.evolve.services.network.tracing.traversal.step_context import *
from zepben.evolve.services.network.tracing.traversal.stop_condition import *
from zepben.evolve.services.network.tracing.traversal.traversal import *
from zepben.evolve.services.network.tracing.traversal.traversal_condition import *
from zepben.evolve.services.network.tracing.traversal.weighted_priority_queue import *

from zepben.evolve.services.network.tracing.find_swer_equipment import *

from zepben.evolve.services.common.meta.data_source import *
from zepben.evolve.services.common.meta.metadata_collection import *
Expand Down Expand Up @@ -426,6 +453,5 @@
from zepben.evolve.services.network.tracing.phases.set_phases import *

from zepben.evolve.testing.test_network_builder import *
from zepben.evolve.testing.test_traversal import *

# @formatter:on
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2024 Zeppelin Bend Pty Ltd
# Copyright 2025 Zeppelin Bend Pty Ltd
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,29 @@ async def _post_load(self) -> bool:
status = await super()._post_load()

self._logger.info("Applying feeder direction to network...")
await self.set_feeder_direction.run(self.service, NetworkStateOperators.NORMAL)
await self.set_feeder_direction.run(self.service, NetworkStateOperators.CURRENT)
await self.set_feeder_direction.run(self.service, network_state_operators=NetworkStateOperators.NORMAL)
await self.set_feeder_direction.run(self.service, network_state_operators=NetworkStateOperators.CURRENT)
self._logger.info("Feeder direction applied to network.")

self._logger.info("Applying phases to network...")
await self.set_phases.run(self.service, NetworkStateOperators.NORMAL)
await self.set_phases.run(self.service, NetworkStateOperators.CURRENT)
await self.set_phases.run(self.service, network_state_operators=NetworkStateOperators.NORMAL)
await self.set_phases.run(self.service, network_state_operators=NetworkStateOperators.CURRENT)
if self.infer_phases:
await self.phase_inferrer.run(self.service, NetworkStateOperators.NORMAL)
await self.phase_inferrer.run(self.service, NetworkStateOperators.CURRENT)
self._log_inferred_phases(
await self.phase_inferrer.run(self.service, network_state_operators=NetworkStateOperators.NORMAL),
await self.phase_inferrer.run(self.service, network_state_operators=NetworkStateOperators.CURRENT)
)

self._logger.info("Phasing applied to network.")

self._logger.info("Assigning equipment to feeders...")
await self.assign_to_feeders.run(self.service, NetworkStateOperators.NORMAL)
await self.assign_to_feeders.run(self.service, NetworkStateOperators.CURRENT)
await self.assign_to_feeders.run(self.service, network_state_operators=NetworkStateOperators.NORMAL)
await self.assign_to_feeders.run(self.service, network_state_operators=NetworkStateOperators.CURRENT)
self._logger.info("Equipment assigned to feeders.")

self._logger.info("Assigning equipment to LV feeders...")
await self.assign_to_lv_feeders.run(self.service, NetworkStateOperators.NORMAL)
await self.assign_to_lv_feeders.run(self.service, NetworkStateOperators.CURRENT)
await self.assign_to_lv_feeders.run(self.service, network_state_operators=NetworkStateOperators.NORMAL)
await self.assign_to_lv_feeders.run(self.service, network_state_operators=NetworkStateOperators.CURRENT)
self._logger.info("Equipment assigned to LV feeders.")

self._logger.info("Validating that each equipment is assigned to a container...")
Expand All @@ -110,16 +113,21 @@ async def _post_load(self) -> bool:

return status

def _log_inferred_phases(self, normal_inferred_phases: List, current_inferred_phases: List): # FIXME: set list contents classes, this'll likely explode until then
# FIXME: im pretty sure this should be building a dict of lists, not just a simple KV store. if so, this logic is way too simple
def _log_inferred_phases(self,
normal_inferred_phases: List[PhaseInferrer.InferredPhase],
current_inferred_phases: List[PhaseInferrer.InferredPhase]):

inferred_phases = {item.conducting_equipment: item for item in normal_inferred_phases}

for it in current_inferred_phases:
ce = it.conducting_equipment
inferred_phases[ce] = (inferred_phases[ce] if inferred_phases[ce].suspect else it)
if it.conducting_equipment in inferred_phases:
left = inferred_phases[it.conducting_equipment]
inferred_phases[it.conducting_equipment] = left if left.suspect else it
else:
inferred_phases[it.conducting_equipment] = it

for phase in inferred_phases:
self._logger.warning(f"*** Action Required *** {phase.description()}")
for phase in inferred_phases.values():
self._logger.warning(f"*** Action Required *** {phase.description}")

def _validate_equipment_containers(self):
missing_containers = [it for it in self.service.objects(Equipment) if not it.containers]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from __future__ import annotations

import sys
from typing import List, Optional, Generator, TYPE_CHECKING
from typing import List, Optional, Generator, TYPE_CHECKING, Union

from zepben.evolve.model.cim.iec61970.base.core.base_voltage import BaseVoltage
from zepben.evolve.model.cim.iec61970.base.core.equipment import Equipment
Expand Down Expand Up @@ -80,12 +80,31 @@ def num_terminals(self):
"""
return len(self._terminals)

def get_terminal(self, identifier: Union[int, str]):
"""
Get the `Terminal` for this `ConductingEquipment` identified by `mrid` or `sequence_number`

:param identifier: the mRID of the required `Terminal`, or the `sequence_number` of the terminal in relation
to this `ConductingEquipment`
:return: The `Terminal` with the specified `mrid` if it exists

Raises `KeyError` if `mrid` wasn't present.
Raises `TypeError` if the identifier wasn't a recognised type
"""
if isinstance(identifier, int):
return self.get_terminal_by_sn(identifier)
elif isinstance(identifier, str):
return self.get_terminal_by_mrid(identifier)
raise TypeError(f'`identifier` parameter not a recognised type: {type(identifier)}')

def get_terminal_by_mrid(self, mrid: str) -> Terminal:
"""
Get the `Terminal` for this `ConductingEquipment` identified by `mrid`

`mrid` the mRID of the required `Terminal`
Returns The `Terminal` with the specified `mrid` if it exists
:param mrid: the mRID of the required `Terminal`

:return: The `Terminal` with the specified `mrid` if it exists

Raises `KeyError` if `mrid` wasn't present.
"""
return get_by_mrid(self._terminals, mrid)
Expand All @@ -94,8 +113,10 @@ def get_terminal_by_sn(self, sequence_number: int):
"""
Get the `Terminal` on this `ConductingEquipment` by its `sequence_number`.

`sequence_number` The `sequence_number` of the `Terminal` in relation to this `ConductingEquipment`.
Returns The `Terminal` on this `ConductingEquipment` with sequence number `sequence_number`
:param sequence_number: The `sequence_number` of the `Terminal` in relation to this `ConductingEquipment`.

:return: The `Terminal` on this `ConductingEquipment` with sequence number `sequence_number`

Raises IndexError if no `Terminal` was found with sequence_number `sequence_number`.
"""
for term in self._terminals:
Expand Down
12 changes: 5 additions & 7 deletions src/zepben/evolve/model/cim/iec61970/base/core/equipment.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,18 @@
from typing import Optional, Generator, List, TYPE_CHECKING, TypeVar, Type

if TYPE_CHECKING:
from zepben.evolve import UsagePoint, EquipmentContainer, OperationalRestriction
from zepben.evolve import UsagePoint, EquipmentContainer, OperationalRestriction, NetworkStateOperators
TEquipmentContainer = TypeVar("TEquipmentContainer", bound=EquipmentContainer)

from zepben.evolve.model.cim.iec61970.base.core.equipment_container import Feeder, Site
from zepben.evolve.model.cim.iec61970.base.core.power_system_resource import PowerSystemResource
from zepben.evolve.model.cim.iec61970.base.core.substation import Substation
from zepben.evolve.model.cim.iec61970.infiec61970.feeder.lv_feeder import LvFeeder
from zepben.evolve.util import nlen, get_by_mrid, ngen, safe_remove
from zepben.evolve.services.network.tracing.networktrace.operators.network_state_operators import NetworkStateOperators

__all__ = ['Equipment']



class Equipment(PowerSystemResource):
"""
Abstract class, should only be used through subclasses.
Expand Down Expand Up @@ -64,11 +62,11 @@ def sites(self) -> Generator[Site, None, None]:
"""
return ngen(_of_type(self._equipment_containers, Site))

def feeders(self, network_state_operators: NetworkStateOperators) -> Generator[Feeder, None, None]:
def feeders(self, network_state_operators: Type[NetworkStateOperators]) -> Generator[Feeder, None, None]:
"""
The `Feeder` this equipment belongs too based on `NetworkStateOperators`
"""
if network_state_operators == NetworkStateOperators.NORMAL:
if network_state_operators.NORMAL:
return self.normal_feeders
else:
return self.current_feeders
Expand All @@ -80,11 +78,11 @@ def normal_feeders(self) -> Generator[Feeder, None, None]:
"""
return ngen(_of_type(self._equipment_containers, Feeder))

def lv_feeders(self, network_state_operators: NetworkStateOperators) -> Generator[LvFeeder, None, None]:
def lv_feeders(self, network_state_operators: Type[NetworkStateOperators]) -> Generator[LvFeeder, None, None]:
"""
The `LvFeeder` this equipment belongs too based on `NetworkStateOperators`
"""
if network_state_operators == NetworkStateOperators.NORMAL:
if network_state_operators.NORMAL:
return self.normal_lv_feeders
else:
return self.current_lv_feeders
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from __future__ import annotations

from typing import Optional, Dict, Generator, List, TYPE_CHECKING, TypeVar, Iterable
from typing import Optional, Dict, Generator, List, TYPE_CHECKING, TypeVar, Iterable, Type

if TYPE_CHECKING:
from zepben.evolve import Equipment, Terminal, Substation, LvFeeder, NetworkStateOperators
Expand Down Expand Up @@ -446,7 +446,7 @@ class Site(EquipmentContainer):
Note this is not a CIM concept - however represents an `EquipmentContainer` in CIM. This is to avoid the use of `EquipmentContainer` as a concrete class.
"""

def find_lv_feeders(self, lv_feeder_start_points: Iterable[ConductingEquipment], state_operators: NetworkStateOperators) -> Generator[LvFeeder, None, None]:
def find_lv_feeders(self, lv_feeder_start_points: Iterable[ConductingEquipment], state_operators: Type[NetworkStateOperators]) -> Generator[LvFeeder, None, None]:
from zepben.evolve.model.cim.iec61970.base.core.conducting_equipment import ConductingEquipment
for ce in state_operators.get_equipment(self):
if isinstance(ce, ConductingEquipment):
Expand Down
2 changes: 1 addition & 1 deletion src/zepben/evolve/model/cim/iec61970/base/core/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,6 @@ def is_feeder_head_terminal(self):

def has_connected_busbars(self):
try:
return any(it != self and it.conducting_equipment is BusbarSection for it in self.connectivity_node.terminals) == True
return any(it != self and isinstance(it.conducting_equipment, BusbarSection) for it in self.connectivity_node.terminals) == True
except AttributeError:
return False
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class AcLineSegment(Conductor):
However, boundary lines may have slightly different BaseVoltage.nominalVoltages and variation is allowed.
Larger voltage difference in general requires use of an equivalent branch.
"""
max_terminals = 2

per_length_impedance: Optional[PerLengthImpedance] = None
"""A `zepben.evolve.PerLengthImpedance` describing this AcLineSegment"""
Expand Down
6 changes: 6 additions & 0 deletions src/zepben/evolve/model/cim/iec61970/base/wires/clamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,9 @@ class Clamp(ConductingEquipment):

ac_line_segment: Optional[AcLineSegment] = None
"""The line segment to which the clamp is connected."""

max_terminals = 1

@property
def length_from_T1_or_0(self) -> float:
return self.length_from_terminal_1 or 0.0
Loading