From eab8dbe28de28082a6fadd2e06ace963ac65ae28 Mon Sep 17 00:00:00 2001 From: Max Chesterfield Date: Tue, 20 May 2025 18:35:55 +1000 Subject: [PATCH 1/3] Do not find LvFeeders starting with open switches in the Site Signed-off-by: Max Chesterfield --- changelog.md | 2 +- .../iec61970/base/core/equipment_container.py | 12 +++--- .../services/network/network_service.py | 12 +++--- .../tracing/feeder/assign_to_feeders.py | 16 +++---- .../tracing/feeder/assign_to_lv_feeders.py | 28 ++++++------ .../network/tracing/networktrace/tracing.py | 2 +- .../network/tracing/phases/phase_inferrer.py | 2 +- .../evolve/testing/test_network_builder.py | 4 ++ test/cim/iec61970/base/core/test_site.py | 43 ++++++++++++++++++- 9 files changed, 84 insertions(+), 37 deletions(-) diff --git a/changelog.md b/changelog.md index b7e034cb3..c112b7ddf 100644 --- a/changelog.md +++ b/changelog.md @@ -10,7 +10,7 @@ * None. ### Fixes -* None. + When finding `LvFeeders` in the `Site` we will now exclude `LvFeeders` that start with an open `Switch` ### Notes * None. diff --git a/src/zepben/evolve/model/cim/iec61970/base/core/equipment_container.py b/src/zepben/evolve/model/cim/iec61970/base/core/equipment_container.py index cb6f24105..f0a6d8b0e 100644 --- a/src/zepben/evolve/model/cim/iec61970/base/core/equipment_container.py +++ b/src/zepben/evolve/model/cim/iec61970/base/core/equipment_container.py @@ -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/. @@ -8,7 +8,8 @@ from typing import Optional, Dict, Generator, List, TYPE_CHECKING, TypeVar, Iterable if TYPE_CHECKING: - from zepben.evolve import Equipment, Terminal, Substation, LvFeeder, ConductingEquipment, NetworkStateOperators + from zepben.evolve import Equipment, Terminal, Substation, LvFeeder, NetworkStateOperators + from zepben.evolve.model.cim.iec61970.base.core.conducting_equipment import ConductingEquipment from zepben.evolve.model.cim.iec61970.base.core.connectivity_node_container import ConnectivityNodeContainer from zepben.evolve.util import nlen, ngen, safe_remove_by_id @@ -445,10 +446,11 @@ 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]: + def find_lv_feeders(self, lv_feeder_start_points: Iterable[ConductingEquipment], state_operators: 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(ConductingEquipment, ce): + if isinstance(ce, ConductingEquipment): if ce in lv_feeder_start_points: - if not state_operators.is_open(ce): + if not state_operators.is_open(ce): # Exclude any open switch that might be energised by a different feeder on the other side for lv_feeder in ce.lv_feeders(state_operators): yield lv_feeder diff --git a/src/zepben/evolve/services/network/network_service.py b/src/zepben/evolve/services/network/network_service.py index 4dff4fd51..abaaf38ab 100644 --- a/src/zepben/evolve/services/network/network_service.py +++ b/src/zepben/evolve/services/network/network_service.py @@ -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/. @@ -11,7 +11,7 @@ import logging from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, Dict, List, Union, Iterable, Optional, Generator +from typing import TYPE_CHECKING, Dict, List, Union, Iterable, Optional, Generator, Set from zepben.evolve.util import ngen @@ -282,9 +282,9 @@ def aux_equipment_by_terminal(self) -> Dict[Terminal, List[AuxiliaryEquipment]]: return eq_by_term @property - def feeder_start_points(self) -> Generator[ConductingEquipment, None, None]: - return ngen(feeder.normal_head_terminal.conducting_equipment for feeder in self.objects(Feeder) if feeder.normal_head_terminal) + def feeder_start_points(self) -> Set[ConductingEquipment]: + return {it.normal_head_terminal.conducting_equipment for it in self.objects(Feeder) if it.normal_head_terminal} @property - def lv_feeder_start_points(self) -> Generator[ConductingEquipment, None, None]: - return ngen(lv_feeder.normal_head_terminal.conducting_equipment for lv_feeder in self.objects(LvFeeder) if lv_feeder.normal_head_terminal) + def lv_feeder_start_points(self) -> Set[ConductingEquipment]: + return {it.normal_head_terminal.conducting_equipment for it in self.objects(LvFeeder) if it.normal_head_terminal} diff --git a/src/zepben/evolve/services/network/tracing/feeder/assign_to_feeders.py b/src/zepben/evolve/services/network/tracing/feeder/assign_to_feeders.py index 2d8fbf7f0..63a45c35d 100644 --- a/src/zepben/evolve/services/network/tracing/feeder/assign_to_feeders.py +++ b/src/zepben/evolve/services/network/tracing/feeder/assign_to_feeders.py @@ -1,9 +1,9 @@ -# 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/. from collections.abc import Collection -from typing import Iterable, Generator, Union, List, Dict, Any +from typing import Iterable, Generator, Union, List, Dict, Any, Set from zepben.evolve import Switch, AuxiliaryEquipment, ProtectedSwitch, Equipment, LvFeeder from zepben.evolve.model.cim.iec61970.base.core.conducting_equipment import ConductingEquipment @@ -72,7 +72,7 @@ def _feeder_energizes(self, feeders: Iterable[Union[LvFeeder, Feeder]], lv_feede for lv_feeder in lv_feeders: self.network_state_operators.associate_energizing_feeder(feeder, lv_feeder) - def _feeder_try_energize_lv_feeders(self, feeders: Iterable[Feeder], lv_feeder_start_points: Generator[ConductingEquipment, None, None], to_equipment: PowerTransformer): + def _feeder_try_energize_lv_feeders(self, feeders: Iterable[Feeder], lv_feeder_start_points: Set[ConductingEquipment], to_equipment: PowerTransformer): sites = [] for eq in to_equipment: sites.extend(eq.sites) @@ -114,8 +114,8 @@ async def run(self, async def run_with_feeders(self, terminal: Terminal, - feeder_start_points: Generator[ConductingEquipment, None, None], - lv_feeder_start_points: Generator[ConductingEquipment, None, None], + feeder_start_points: Set[ConductingEquipment], + lv_feeder_start_points: Set[ConductingEquipment], terminal_to_aux_equipment: Dict[Terminal, List[AuxiliaryEquipment]], feeders_to_assign: List[Feeder]): @@ -132,8 +132,8 @@ async def run_with_feeders(self, async def _create_trace(self, terminal_to_aux_equipment: Dict[Terminal, List[AuxiliaryEquipment]], - feeder_start_points: Generator[ConductingEquipment, None, None], - lv_feeder_start_points: Generator[ConductingEquipment, None, None], + feeder_start_points: Set[ConductingEquipment], + lv_feeder_start_points: Set[ConductingEquipment], feeders_to_assign: List[Feeder]) -> NetworkTrace[Any]: def _reached_lv(ce: ConductingEquipment): @@ -159,7 +159,7 @@ async def _process(self, step_path: NetworkTraceStep.Path, step_context: StepContext, terminal_to_aux_equipment: Dict[Terminal, Collection[AuxiliaryEquipment]], - lv_feeder_start_points: Generator[ConductingEquipment, None, None], + lv_feeder_start_points: Set[ConductingEquipment], feeders_to_assign: List[Feeder]): if step_path.traced_internally and not step_context.is_start_item: diff --git a/src/zepben/evolve/services/network/tracing/feeder/assign_to_lv_feeders.py b/src/zepben/evolve/services/network/tracing/feeder/assign_to_lv_feeders.py index 6497bd1c6..16a427c2e 100644 --- a/src/zepben/evolve/services/network/tracing/feeder/assign_to_lv_feeders.py +++ b/src/zepben/evolve/services/network/tracing/feeder/assign_to_lv_feeders.py @@ -1,10 +1,9 @@ -# 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/. -from collections.abc import Iterable -from typing import Collection, List, Generator, TypeVar, Dict +from typing import Collection, List, Generator, TypeVar, Dict, Set from zepben.evolve import Switch, AuxiliaryEquipment, ProtectedSwitch from zepben.evolve.model.cim.iec61970.base.core.conducting_equipment import ConductingEquipment @@ -64,6 +63,7 @@ async def run(self, for feeder in head_equipment.feeders(self.network_state_operators): self.network_state_operators.associate_energizing_feeder(feeder, lv_feeder) + # We can run from each LV feeder as we process them, as being associated with their energizing feeders is not a requirement of the trace. await self.run_with_feeders(lv_feeder.normal_head_terminal, lv_feeder_start_points, terminal_to_aux_equipment, @@ -77,7 +77,7 @@ async def run(self, async def run_with_feeders(self, terminal: Terminal, - lv_feeder_start_points: Iterable[ConductingEquipment], + lv_feeder_start_points: Set[ConductingEquipment], terminal_to_aux_equipment: Dict[Terminal, List[AuxiliaryEquipment]], lv_feeders_to_assign: List[LvFeeder]): @@ -89,12 +89,12 @@ async def run_with_feeders(self, if isinstance(start_ce, Switch) and self.network_state_operators.is_open(start_ce): self._associate_equipment_with_containers(lv_feeders_to_assign, [start_ce]) else: - traversal = await self._create_trace(terminal_to_aux_equipment, lv_feeder_start_points, lv_feeders_to_assign) + traversal = self._create_trace(terminal_to_aux_equipment, lv_feeder_start_points, lv_feeders_to_assign) await traversal.run(terminal, False) - async def _create_trace(self, + def _create_trace(self, terminal_to_aux_equipment: Dict[Terminal, List[AuxiliaryEquipment]], - lv_feeder_start_points: Iterable[ConductingEquipment], + lv_feeder_start_points: Set[ConductingEquipment], lv_feeders_to_assign: List[LvFeeder]) -> NetworkTrace[T]: def _reached_hv(ce: ConductingEquipment): @@ -123,19 +123,19 @@ async def _process(self, found_lv_feeder: bool, step_context: StepContext, terminal_to_aux_equipment: Dict[Terminal, Collection[AuxiliaryEquipment]], - lv_feeder_start_points: Iterable[ConductingEquipment], + lv_feeder_start_points: Set[ConductingEquipment], lv_feeders_to_assign: List[LvFeeder]): if step_path.traced_internally and not step_context.is_start_item: return if found_lv_feeder: - found_lv_feeders = self._find_lv_feeders(step_path.to_equipment, lv_feeder_start_points) + found_lv_feeders = list(self._find_lv_feeders(step_path.to_equipment, lv_feeder_start_points)) - energizing_feeders = list(self.network_state_operators.get_energizing_feeders(it) for it in found_lv_feeders) - for feeder_group in (lv_feeders_to_assign, found_lv_feeders): - self._feeder_energizes(feeder_group, energizing_feeders) + for energizing_feeder in (self.network_state_operators.get_energizing_feeders(it) for it in found_lv_feeders): + for feeder_group in (lv_feeders_to_assign, found_lv_feeders): + self._feeder_energizes(feeder_group, energizing_feeder) try: aux_equip_for_this_terminal = terminal_to_aux_equipment[step_path.to_terminal] @@ -148,7 +148,7 @@ async def _process(self, if isinstance(step_path.to_equipment, ProtectedSwitch): self._associate_relay_systems_with_containers(lv_feeders_to_assign, step_path.to_equipment) - def _find_lv_feeders(self, ce: ConductingEquipment, lv_feeder_start_points: Iterable[ConductingEquipment]) -> Generator[LvFeeder, None, None]: + def _find_lv_feeders(self, ce: ConductingEquipment, lv_feeder_start_points: Set[ConductingEquipment]) -> Generator[LvFeeder, None, None]: sites = list(ce.sites) if sites: for site in sites: @@ -159,4 +159,4 @@ def _find_lv_feeders(self, ce: ConductingEquipment, lv_feeder_start_points: Iter yield feeder def _lv_feeders_from_terminal(self, terminal: Terminal) -> List[LvFeeder]: - return terminal.conducting_equipment.lv_feeders(self.network_state_operators) + return list(terminal.conducting_equipment.lv_feeders(self.network_state_operators)) diff --git a/src/zepben/evolve/services/network/tracing/networktrace/tracing.py b/src/zepben/evolve/services/network/tracing/networktrace/tracing.py index 65629482d..37a08009e 100644 --- a/src/zepben/evolve/services/network/tracing/networktrace/tracing.py +++ b/src/zepben/evolve/services/network/tracing/networktrace/tracing.py @@ -19,7 +19,7 @@ class Tracing: def network_trace(network_state_operators: NetworkStateOperators=NetworkStateOperators.NORMAL, action_step_type: NetworkTraceActionType=NetworkTraceActionType.FIRST_STEP_ON_EQUIPMENT, queue: TraversalQueue[NetworkTraceStep[T]]=TraversalQueue.depth_first(), - compute_data: ComputeData[T]=None + compute_data: Union[ComputeData[T], Callable]=None ) -> NetworkTrace[T]: """ Creates a `NetworkTrace` that computes contextual data for every step. diff --git a/src/zepben/evolve/services/network/tracing/phases/phase_inferrer.py b/src/zepben/evolve/services/network/tracing/phases/phase_inferrer.py index 2febe02e5..e9c4bb61a 100644 --- a/src/zepben/evolve/services/network/tracing/phases/phase_inferrer.py +++ b/src/zepben/evolve/services/network/tracing/phases/phase_inferrer.py @@ -75,7 +75,7 @@ def _has_none_phase(self, terminal: Terminal) -> bool: @staticmethod def _has_xy_phases(terminal: Terminal) -> bool: - return any(p in terminal.phases for p in (SinglePhaseKind.X, SinglePhaseKind.Y)) + return SinglePhaseKind.Y in terminal.phases.single_phases or SinglePhaseKind.X in terminal.phases.single_phases def _find_terminal_at_start_of_missing_phases( self, diff --git a/src/zepben/evolve/testing/test_network_builder.py b/src/zepben/evolve/testing/test_network_builder.py index 890fee5e7..1ef758be3 100644 --- a/src/zepben/evolve/testing/test_network_builder.py +++ b/src/zepben/evolve/testing/test_network_builder.py @@ -612,7 +612,9 @@ def _create_feeder(self, mrid: Optional[str], head_equipment: ConductingEquipmen ) f.add_equipment(head_equipment) + f.add_current_equipment(head_equipment) head_equipment.add_container(f) + head_equipment.add_current_container(f) self.network.add(f) return f @@ -624,7 +626,9 @@ def _create_lv_feeder(self, mrid: Optional[str], head_equipment: ConductingEquip ) lvf.add_equipment(head_equipment) + lvf.add_current_equipment(head_equipment) head_equipment.add_container(lvf) + head_equipment.add_current_container(lvf) self.network.add(lvf) return lvf diff --git a/test/cim/iec61970/base/core/test_site.py b/test/cim/iec61970/base/core/test_site.py index 901548bf6..994a3ac63 100644 --- a/test/cim/iec61970/base/core/test_site.py +++ b/test/cim/iec61970/base/core/test_site.py @@ -2,11 +2,13 @@ # 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/. +import pytest from hypothesis import given +from build.lib.zepben.evolve.services.network.tracing.networktrace.operators.network_state_operators import NetworkStateOperators from cim.iec61970.base.core.test_equipment_container import equipment_container_kwargs, verify_equipment_container_constructor_default, \ verify_equipment_container_constructor_kwargs, verify_equipment_container_constructor_args, equipment_container_args -from zepben.evolve import Site +from zepben.evolve import Site, TestNetworkBuilder, Equipment, AssignToLvFeeders, LvFeeder site_kwargs = equipment_container_kwargs site_args = equipment_container_args @@ -23,3 +25,42 @@ def test_site_constructor_kwargs(**kwargs): def test_site_constructor_args(): verify_equipment_container_constructor_args(Site(*site_args)) + +@pytest.mark.asyncio +async def test_find_lv_feeders_excludes_open_switches(): + # + # tx0 21 b1(lvf5) 21--c2--2 + # 21 b3(lvf6) 21--c4--2 + # + site = Site() + network = (TestNetworkBuilder() + .from_power_transformer(action=lambda pt: _add_to_site(pt, site)) # tx0 + .from_breaker(is_normally_open=True, is_open=True, action=lambda b: _add_to_site(b, site)) # b1 + .to_acls() # c2 + .branch_from('tx0') + .to_breaker(is_normally_open=False, is_open=False, action=lambda b: _add_to_site(b, site)) # b3 + .to_acls() # c4 + .add_lv_feeder('b1') # lvf5 + .add_lv_feeder('b3') # lvf6 + ).network + + assign_to_lv_feeders = AssignToLvFeeders() + + await assign_to_lv_feeders.run(network, network_state_operators=NetworkStateOperators.NORMAL, start_terminal=network.get('b1-t2')) + await assign_to_lv_feeders.run(network, network_state_operators=NetworkStateOperators.NORMAL, start_terminal=network.get('b3-t2')) + await assign_to_lv_feeders.run(network, network_state_operators=NetworkStateOperators.CURRENT, start_terminal=network.get('b1-t2')) + await assign_to_lv_feeders.run(network, network_state_operators=NetworkStateOperators.CURRENT, start_terminal=network.get('b3-t2')) + + lvf6 = network.get('lvf6', LvFeeder) + normal_lv_feeders = list(site.find_lv_feeders(network.lv_feeder_start_points, NetworkStateOperators.NORMAL)) + assert normal_lv_feeders == [lvf6] + + current_lv_feeders = list(site.find_lv_feeders(network.lv_feeder_start_points, NetworkStateOperators.CURRENT)) + assert current_lv_feeders == [lvf6] + + +def _add_to_site(equipment: Equipment, site: Site): + site.add_equipment(equipment) + equipment.add_container(site) + site.add_current_equipment(equipment) + equipment.add_current_container(site) \ No newline at end of file From 6140df32821693c954dd5475c66fba9b9609358f Mon Sep 17 00:00:00 2001 From: chestm007 Date: Wed, 21 May 2025 13:58:59 +1000 Subject: [PATCH 2/3] Update assign_to_lv_feeders.py pep8 fix Signed-off-by: Max Chesterfield --- .../services/network/tracing/feeder/assign_to_lv_feeders.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/zepben/evolve/services/network/tracing/feeder/assign_to_lv_feeders.py b/src/zepben/evolve/services/network/tracing/feeder/assign_to_lv_feeders.py index 16a427c2e..5c6ac8099 100644 --- a/src/zepben/evolve/services/network/tracing/feeder/assign_to_lv_feeders.py +++ b/src/zepben/evolve/services/network/tracing/feeder/assign_to_lv_feeders.py @@ -132,7 +132,6 @@ async def _process(self, if found_lv_feeder: found_lv_feeders = list(self._find_lv_feeders(step_path.to_equipment, lv_feeder_start_points)) - for energizing_feeder in (self.network_state_operators.get_energizing_feeders(it) for it in found_lv_feeders): for feeder_group in (lv_feeders_to_assign, found_lv_feeders): self._feeder_energizes(feeder_group, energizing_feeder) From 6289eca64d6c499250d06f7a1e9bbb3b0d311788 Mon Sep 17 00:00:00 2001 From: vincewhite <33650891+vincewhite@users.noreply.github.com> Date: Wed, 21 May 2025 14:13:08 +1000 Subject: [PATCH 3/3] Update test/cim/iec61970/base/core/test_site.py Signed-off-by: vincewhite <33650891+vincewhite@users.noreply.github.com> --- test/cim/iec61970/base/core/test_site.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/cim/iec61970/base/core/test_site.py b/test/cim/iec61970/base/core/test_site.py index 994a3ac63..daf68563e 100644 --- a/test/cim/iec61970/base/core/test_site.py +++ b/test/cim/iec61970/base/core/test_site.py @@ -63,4 +63,5 @@ def _add_to_site(equipment: Equipment, site: Site): site.add_equipment(equipment) equipment.add_container(site) site.add_current_equipment(equipment) - equipment.add_current_container(site) \ No newline at end of file + equipment.add_current_container(site) + \ No newline at end of file