diff --git a/include/powsybl/iidm/Terminal.hpp b/include/powsybl/iidm/Terminal.hpp index 08216609..c0ddf763 100644 --- a/include/powsybl/iidm/Terminal.hpp +++ b/include/powsybl/iidm/Terminal.hpp @@ -88,7 +88,7 @@ class Terminal : public MultiVariantObject { virtual void traverse(voltage_level::TopologyTraverser& traverser) = 0; - virtual void traverse(voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) = 0; + virtual bool traverse(voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) = 0; protected: // MultiVariantObject void allocateVariantArrayElement(const std::set& indexes, unsigned long sourceIndex) override; diff --git a/include/powsybl/iidm/VoltageLevelTopologyTraverser.hpp b/include/powsybl/iidm/VoltageLevelTopologyTraverser.hpp index 86c99010..e5cdff73 100644 --- a/include/powsybl/iidm/VoltageLevelTopologyTraverser.hpp +++ b/include/powsybl/iidm/VoltageLevelTopologyTraverser.hpp @@ -8,6 +8,8 @@ #ifndef POWSYBL_IIDM_VOLTAGELEVELTOPOLOGYTRAVERSER_HPP #define POWSYBL_IIDM_VOLTAGELEVELTOPOLOGYTRAVERSER_HPP +#include + namespace powsybl { namespace iidm { @@ -31,9 +33,9 @@ class TopologyTraverser { TopologyTraverser& operator=(TopologyTraverser&&) noexcept = default; - virtual bool traverse(Terminal& terminal, bool connected) = 0; + virtual math::TraverseResult traverse(Terminal& terminal, bool connected) = 0; - virtual bool traverse(Switch& aSwitch) = 0; + virtual math::TraverseResult traverse(Switch& aSwitch) = 0; }; } // namespace voltage_level diff --git a/src/iidm/BusBreakerVoltageLevel.cpp b/src/iidm/BusBreakerVoltageLevel.cpp index 21890d4b..32423bff 100644 --- a/src/iidm/BusBreakerVoltageLevel.cpp +++ b/src/iidm/BusBreakerVoltageLevel.cpp @@ -266,6 +266,11 @@ const TopologyKind& BusBreakerVoltageLevel::getTopologyKind() const { return s_topologyKind; } +math::TraverseResult BusBreakerVoltageLevel::getTraverserResult(TerminalSet& visitedTerminals, BusTerminal& terminal, TopologyTraverser& traverser) { + auto pair = visitedTerminals.insert(terminal); + return pair.second ? traverser.traverse(terminal, true) : math::TraverseResult::TERMINATE_PATH; +} + stdcxx::optional BusBreakerVoltageLevel::getVertex(const std::string& busId, bool throwException) const { checkNotEmpty(busId, "bus id is null"); @@ -366,52 +371,57 @@ void BusBreakerVoltageLevel::traverse(BusTerminal& terminal, VoltageLevel::Topol traverse(terminal, traverser, traversedTerminals); } -void BusBreakerVoltageLevel::traverse(BusTerminal& terminal, VoltageLevel::TopologyTraverser& traverser, TerminalSet& traversedTerminals) const { - if (traversedTerminals.find(terminal) != traversedTerminals.end()) { - return; - } - - TerminalSet nextTerminals; - +bool BusBreakerVoltageLevel::traverse(BusTerminal& terminal, VoltageLevel::TopologyTraverser& traverser, TerminalSet& traversedTerminals) const { // check if we are allowed to traverse the terminal itself - if (traverser.traverse(terminal, terminal.isConnected())) { - traversedTerminals.emplace(terminal); + math::TraverseResult termTraverseResult = getTraverserResult(traversedTerminals, terminal, traverser); + if (termTraverseResult == math::TraverseResult::TERMINATE_TRAVERSER) { + return false; + } + if (termTraverseResult == math::TraverseResult::CONTINUE) { + TerminalSet nextTerminals; addNextTerminals(terminal, nextTerminals); - // then check we can traverse terminal connected to same bus + // then check we can traverse terminals connected to same bus unsigned long v = *getVertex(terminal.getConnectableBusId(), true); - ConfiguredBus& bus = m_graph.getVertexObject(v); - for (Terminal& t : bus.getTerminals()) { - if (!stdcxx::areSame(t, terminal) && traverser.traverse(t, t.isConnected())) { + ConfiguredBus& bus = m_graph.getVertexObject(v).get(); + for (BusTerminal& t : bus.getTerminals()) { + math::TraverseResult tTraverseResult = getTraverserResult(traversedTerminals, t, traverser); + if (tTraverseResult == math::TraverseResult::TERMINATE_TRAVERSER) { + return false; + } + if (tTraverseResult == math::TraverseResult::CONTINUE) { addNextTerminals(t, nextTerminals); } } // then go through other buses of the voltage level - m_graph.traverse(v, [this, &traverser, &traversedTerminals, &nextTerminals](unsigned long /*v1*/, unsigned long e, unsigned long v2) { + bool traversalTerminated = !m_graph.traverse(v, [this, &nextTerminals, &traverser, &traversedTerminals](unsigned long /*v1*/, unsigned long e, unsigned long v2) { Switch& aSwitch = m_graph.getEdgeObject(e); - ConfiguredBus& otherBus = m_graph.getVertexObject(v2); - if (traverser.traverse(aSwitch)) { - if (otherBus.getTerminalCount() == 0) { - return math::TraverseResult::CONTINUE; - } - - BusTerminal& otherTerminal = *otherBus.getTerminals().begin(); - if (traverser.traverse(otherTerminal, otherTerminal.isConnected())) { - traversedTerminals.emplace(otherTerminal); - + const stdcxx::range& otherBusTerminals = m_graph.getVertexObject(v2).get().getTerminals(); + math::TraverseResult switchTraverseResult = traverser.traverse(aSwitch); + if (switchTraverseResult == math::TraverseResult::CONTINUE && !otherBusTerminals.empty()) { + BusTerminal& otherTerminal = *otherBusTerminals.begin(); + math::TraverseResult otherTermTraverseResult = getTraverserResult(traversedTerminals, otherTerminal, traverser); + if (otherTermTraverseResult == math::TraverseResult::CONTINUE) { addNextTerminals(otherTerminal, nextTerminals); - return math::TraverseResult::CONTINUE; } + return otherTermTraverseResult; } - return math::TraverseResult::TERMINATE_PATH; + return switchTraverseResult; }); + if (traversalTerminated) { + return false; + } - for (Terminal& t : nextTerminals) { - t.traverse(traverser, traversedTerminals); + for (Terminal& terminal : nextTerminals) { + if (!terminal.traverse(traverser, traversedTerminals)) { + return false; + } } } + + return true; } } // namespace iidm diff --git a/src/iidm/BusBreakerVoltageLevel.hpp b/src/iidm/BusBreakerVoltageLevel.hpp index 1316fa76..b3570d83 100644 --- a/src/iidm/BusBreakerVoltageLevel.hpp +++ b/src/iidm/BusBreakerVoltageLevel.hpp @@ -68,7 +68,7 @@ class BusBreakerVoltageLevel : public VoltageLevel { void traverse(BusTerminal& terminal, voltage_level::TopologyTraverser& traverser) const; - void traverse(BusTerminal& terminal, voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) const; + bool traverse(BusTerminal& terminal, voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) const; protected: // MultiVariantObject void allocateVariantArrayElement(const std::set& indexes, unsigned long sourceIndex) override; @@ -91,6 +91,9 @@ class BusBreakerVoltageLevel : public VoltageLevel { void removeTopology() override; +private: + static math::TraverseResult getTraverserResult(TerminalSet& visitedTerminals, BusTerminal& terminal, TopologyTraverser& traverser); + private: void checkTerminal(Terminal& terminal) const; diff --git a/src/iidm/BusTerminal.cpp b/src/iidm/BusTerminal.cpp index b02f725c..a68545b6 100644 --- a/src/iidm/BusTerminal.cpp +++ b/src/iidm/BusTerminal.cpp @@ -116,11 +116,10 @@ void BusTerminal::traverse(voltage_level::TopologyTraverser& traverser) { dynamic_cast(getVoltageLevel()).traverse(*this, traverser); } -void BusTerminal::traverse(voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) { - dynamic_cast(getVoltageLevel()).traverse(*this, traverser, traversedTerminals); +bool BusTerminal::traverse(voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) { + return dynamic_cast(getVoltageLevel()).traverse(*this, traverser, traversedTerminals); } - std::ostream& operator<<(std::ostream& stream, const BusTerminal& busTerminal) { stream << stdcxx::simpleClassName(busTerminal) << "[" << busTerminal.getConnectableBusId() << "]"; diff --git a/src/iidm/BusTerminal.hpp b/src/iidm/BusTerminal.hpp index ae19e82d..61ec36b7 100644 --- a/src/iidm/BusTerminal.hpp +++ b/src/iidm/BusTerminal.hpp @@ -50,7 +50,7 @@ class BusTerminal : public Terminal { void traverse(voltage_level::TopologyTraverser& traverser) override; - void traverse(voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) override; + bool traverse(voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) override; public: BusTerminal(VoltageLevel& voltageLevel, const std::string& connectableBusId, bool connected); diff --git a/src/iidm/NodeBreakerVoltageLevel.cpp b/src/iidm/NodeBreakerVoltageLevel.cpp index 50e1555c..01d1676e 100644 --- a/src/iidm/NodeBreakerVoltageLevel.cpp +++ b/src/iidm/NodeBreakerVoltageLevel.cpp @@ -333,6 +333,11 @@ const TopologyKind& NodeBreakerVoltageLevel::getTopologyKind() const { return s_topologyKind; } +math::TraverseResult NodeBreakerVoltageLevel::getTraverseResult(TerminalSet& visitedTerminals, NodeTerminal& terminal, TopologyTraverser& traverser) { + auto pair = visitedTerminals.insert(terminal); + return pair.second ? traverser.traverse(terminal, true) : math::TraverseResult::TERMINATE_PATH; +} + void NodeBreakerVoltageLevel::invalidateCache() { m_variants.get().getCalculatedBusTopology().invalidateCache(); m_variants.get().getCalculatedBusBreakerTopology().invalidateCache(); @@ -395,42 +400,42 @@ void NodeBreakerVoltageLevel::traverse(NodeTerminal& terminal, VoltageLevel::Top traverse(terminal, traverser, traversedTerminals); } -void NodeBreakerVoltageLevel::traverse(NodeTerminal& terminal, VoltageLevel::TopologyTraverser& traverser, TerminalSet& traversedTerminals) const { - if (traversedTerminals.find(terminal) != traversedTerminals.end()) { - return; +bool NodeBreakerVoltageLevel::traverse(NodeTerminal& terminal, VoltageLevel::TopologyTraverser& traverser, TerminalSet& traversedTerminals) const { + const math::TraverseResult& termTraverseResult = getTraverseResult(traversedTerminals, terminal, traverser); + if (termTraverseResult == math::TraverseResult::TERMINATE_TRAVERSER) { + return false; } - - if (traverser.traverse(terminal, true)) { - traversedTerminals.emplace(terminal); - - unsigned long node = terminal.getNode(); + if (termTraverseResult == math::TraverseResult::CONTINUE) { TerminalSet nextTerminals; - addNextTerminals(terminal, nextTerminals); - m_graph.traverse(node, [this, &traverser, &traversedTerminals, &nextTerminals](unsigned long /*v1*/, unsigned long e, unsigned long v2) { - const stdcxx::Reference& aSwitch = m_graph.getEdgeObject(e); - const stdcxx::Reference& otherTerminal = m_graph.getVertexObject(v2); - if (!aSwitch // internal connection case - || traverser.traverse(aSwitch)) { - if (!otherTerminal) { - return math::TraverseResult::CONTINUE; - } - if (traverser.traverse(otherTerminal.get(), true)) { - traversedTerminals.emplace(otherTerminal.get()); - - addNextTerminals(otherTerminal.get(), nextTerminals); - return math::TraverseResult::CONTINUE; + unsigned long node = terminal.getNode(); + bool traverseTerminated = !m_graph.traverse(node, [this, &traverser, &traversedTerminals, &nextTerminals](unsigned long /*v1*/, unsigned long e, unsigned long v2) { + const auto& aSwitch = m_graph.getEdgeObject(e); + const auto& otherTerminal = m_graph.getVertexObject(v2); + const math::TraverseResult& edgeTraverseResult = aSwitch ? traverser.traverse(aSwitch) : math::TraverseResult::CONTINUE; // internal connection case + if (edgeTraverseResult == math::TraverseResult::CONTINUE && otherTerminal) { + math::TraverseResult otherTermTraverseResult = getTraverseResult(traversedTerminals, otherTerminal, traverser); + if (otherTermTraverseResult == math::TraverseResult::CONTINUE) { + addNextTerminals(otherTerminal, nextTerminals); } - return math::TraverseResult::TERMINATE_PATH; + return otherTermTraverseResult; } - return math::TraverseResult::TERMINATE_PATH; + return edgeTraverseResult; }); - for (auto nextTerminal : nextTerminals) { - nextTerminal.get().traverse(traverser, traversedTerminals); + if (traverseTerminated) { + return false; + } + + for (Terminal& nextTerminal : nextTerminals) { + if (!nextTerminal.traverse(traverser, traversedTerminals)) { + return false; + } } } + + return true; } } // namespace iidm diff --git a/src/iidm/NodeBreakerVoltageLevel.hpp b/src/iidm/NodeBreakerVoltageLevel.hpp index 264f3142..9f5b57f9 100644 --- a/src/iidm/NodeBreakerVoltageLevel.hpp +++ b/src/iidm/NodeBreakerVoltageLevel.hpp @@ -66,7 +66,7 @@ class NodeBreakerVoltageLevel : public VoltageLevel { void traverse(NodeTerminal& terminal, VoltageLevel::TopologyTraverser& traverser) const; - void traverse(NodeTerminal& terminal, VoltageLevel::TopologyTraverser& traverser, TerminalSet& traversedTerminals) const; + bool traverse(NodeTerminal& terminal, VoltageLevel::TopologyTraverser& traverser, TerminalSet& traversedTerminals) const; public: NodeBreakerVoltageLevel(const std::string& id, const std::string& name, bool fictitious, const stdcxx::Reference& substation, @@ -97,6 +97,9 @@ class NodeBreakerVoltageLevel : public VoltageLevel { private: // VoltageLevel void removeTopology() override; +private: + static math::TraverseResult getTraverseResult(TerminalSet& visitedTerminals, NodeTerminal& terminal, TopologyTraverser& traverser); + private: void checkTerminal(Terminal& terminal) const; diff --git a/src/iidm/NodeTerminal.cpp b/src/iidm/NodeTerminal.cpp index ae310550..9247a13f 100644 --- a/src/iidm/NodeTerminal.cpp +++ b/src/iidm/NodeTerminal.cpp @@ -139,8 +139,8 @@ void NodeTerminal::traverse(voltage_level::TopologyTraverser& traverser) { dynamic_cast(getVoltageLevel()).traverse(*this, traverser); } -void NodeTerminal::traverse(voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) { - dynamic_cast(getVoltageLevel()).traverse(*this, traverser, traversedTerminals); +bool NodeTerminal::traverse(voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) { + return dynamic_cast(getVoltageLevel()).traverse(*this, traverser, traversedTerminals); } } // namespace iidm diff --git a/src/iidm/NodeTerminal.hpp b/src/iidm/NodeTerminal.hpp index be8c1a93..faf2d94e 100644 --- a/src/iidm/NodeTerminal.hpp +++ b/src/iidm/NodeTerminal.hpp @@ -48,7 +48,7 @@ class NodeTerminal : public Terminal { void traverse(voltage_level::TopologyTraverser& traverser) override; - void traverse(voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) override; + bool traverse(voltage_level::TopologyTraverser& traverser, TerminalSet& traversedTerminals) override; public: NodeTerminal(VoltageLevel& voltageLevel, unsigned long node); diff --git a/test/iidm/CMakeLists.txt b/test/iidm/CMakeLists.txt index 04f32ca7..829f273d 100644 --- a/test/iidm/CMakeLists.txt +++ b/test/iidm/CMakeLists.txt @@ -42,6 +42,7 @@ set(UNIT_TEST_SOURCES SubstationTest.cpp TerminalTest.cpp TopologyLevelTest.cpp + TopologyTraverserTest.cpp ThreeWindingsTransformerTest.cpp TieLineTest.cpp TwoWindingsTransformerTest.cpp diff --git a/test/iidm/NetworkFactory.cpp b/test/iidm/NetworkFactory.cpp index 687b7d2b..ba61dc43 100644 --- a/test/iidm/NetworkFactory.cpp +++ b/test/iidm/NetworkFactory.cpp @@ -202,6 +202,150 @@ Network createNetworkTest1() { return network; } +Network createMixedNodeBreakerBusBreakerNetwork() { + Network network = createNodeBreakerNetwork(); + Substation& s3 = network.newSubstation() + .setId("S3") + .setCountry(Country::FR) + .add(); + VoltageLevel& vl3 = s3.newVoltageLevel() + .setId("VL3") + .setNominalV(400.0) + .setTopologyKind(TopologyKind::BUS_BREAKER) + .add(); + vl3.getBusBreakerView().newBus() + .setId("B1") + .add(); + vl3.newLoad() + .setId("LD2") + .setConnectableBus("B1") + .setBus("B1") + .setP0(1.0) + .setQ0(1.0) + .add(); + network.getVoltageLevel("VL2").getNodeBreakerView().newBreaker() + .setId("BR5") + .setNode1(0) + .setNode2(4) + .setOpen(false) + .add(); + network.newLine() + .setId("L2") + .setVoltageLevel1("VL2") + .setNode1(4) + .setVoltageLevel2("VL3") + .setConnectableBus2("B1") + .setBus2("B1") + .setR(1.0) + .setX(1.0) + .setG1(0.0) + .setB1(0.0) + .setG2(0.0) + .setB2(0.0) + .add(); + return network; +} + +Network createNodeBreakerNetwork() { + Network network("test", "test"); + Substation& s1 = network.newSubstation() + .setId("S1") + .setCountry(Country::FR) + .add(); + VoltageLevel& vl1 = s1.newVoltageLevel() + .setId("VL1") + .setNominalV(400.0) + .setTopologyKind(TopologyKind::NODE_BREAKER) + .add(); + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS1") + .setNode(0) + .add(); + vl1.newGenerator() + .setId("G") + .setNode(4) + .setMaxP(100.0) + .setMinP(50.0) + .setTargetP(100.0) + .setTargetV(400.0) + .setVoltageRegulatorOn(true) + .add(); + vl1.getNodeBreakerView().newInternalConnection() + .setNode1(1) + .setNode2(4) + .add(); + vl1.getNodeBreakerView().newDisconnector() + .setId("BR1") + .setNode1(0) + .setNode2(1) + .setOpen(false) + .add(); + vl1.getNodeBreakerView().newDisconnector() + .setId("D1") + .setNode1(0) + .setNode2(2) + .setOpen(false) + .add(); + vl1.getNodeBreakerView().newBreaker() + .setId("BR2") + .setNode1(2) + .setNode2(3) + .setOpen(false) + .add(); + + Substation& s2 = network.newSubstation() + .setId("S2") + .setCountry(Country::FR) + .add(); + VoltageLevel& vl2 = s2.newVoltageLevel() + .setId("VL2") + .setNominalV(400.0) + .setTopologyKind(TopologyKind::NODE_BREAKER) + .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("BBS2") + .setNode(0) + .add(); + vl2.newLoad() + .setId("LD") + .setNode(1) + .setP0(1) + .setQ0(1) + .add(); + vl2.getNodeBreakerView().newDisconnector() + .setId("BR3") + .setNode1(0) + .setNode2(1) + .setOpen(false) + .add(); + vl2.getNodeBreakerView().newDisconnector() + .setId("D2") + .setNode1(0) + .setNode2(2) + .setOpen(false) + .add(); + vl2.getNodeBreakerView().newBreaker() + .setId("BR4") + .setNode1(2) + .setNode2(3) + .setOpen(false) + .add(); + network.newLine() + .setId("L1") + .setVoltageLevel1("VL1") + .setNode1(3) + .setVoltageLevel2("VL2") + .setNode2(3) + .setR(1.0) + .setX(1.0) + .setG1(0.0) + .setB1(0.0) + .setG2(0.0) + .setB2(0.0) + .add(); + return network; +} + Network createSwitchBBKNetwork() { Network network("test", "test"); Substation& substation = network.newSubstation() @@ -397,34 +541,6 @@ Network createSwitchBBKNetwork() { return network; } -Terminal& getTerminalFromNetwork2() { - if(network2.getSubstationCount() == 0) { - Substation& s = network2.newSubstation() - .setId("S") - .setCountry(Country::FR) - .add(); - - VoltageLevel& vl = s.newVoltageLevel() - .setId("VL") - .setTopologyKind(TopologyKind::NODE_BREAKER) - .setNominalV(400.0) - .setLowVoltageLimit(380.0) - .setHighVoltageLimit(420.0) - .add(); - - vl.newLoad() - .setId("LOAD1") - .setNode(0) - .setName("LOAD1_NAME") - .setLoadType(LoadType::UNDEFINED) - .setP0(50.0) - .setQ0(40.0) - .add(); - } - - return network2.getLoad("LOAD1").getTerminal(); -} - Network createComponentsTestNetworkBB() { Network network("testBB", "testBB"); Substation& substation = network.newSubstation() @@ -1173,6 +1289,34 @@ Network createComponentsTestNetworkNB() { return network; } +Terminal& getTerminalFromNetwork2() { + if(network2.getSubstationCount() == 0) { + Substation& s = network2.newSubstation() + .setId("S") + .setCountry(Country::FR) + .add(); + + VoltageLevel& vl = s.newVoltageLevel() + .setId("VL") + .setTopologyKind(TopologyKind::NODE_BREAKER) + .setNominalV(400.0) + .setLowVoltageLimit(380.0) + .setHighVoltageLimit(420.0) + .add(); + + vl.newLoad() + .setId("LOAD1") + .setNode(0) + .setName("LOAD1_NAME") + .setLoadType(LoadType::UNDEFINED) + .setP0(50.0) + .setQ0(40.0) + .add(); + } + + return network2.getLoad("LOAD1").getTerminal(); +} + } // namespace iidm } // namespace powsybl diff --git a/test/iidm/NetworkFactory.hpp b/test/iidm/NetworkFactory.hpp index 3ee4bad3..749aaecd 100644 --- a/test/iidm/NetworkFactory.hpp +++ b/test/iidm/NetworkFactory.hpp @@ -21,16 +21,18 @@ Network createComponentsTestNetworkNB(); Network createHvdcConverterStationTestNetwork(); +Network createMixedNodeBreakerBusBreakerNetwork(); + Network createNetwork(); Network createNetworkTest1(); +Network createNodeBreakerNetwork(); + Network createSwitchBBKNetwork(); Terminal& getTerminalFromNetwork2(); -Network createDanglingLineNetwork(); - } // namespace iidm } // namespace powsybl diff --git a/test/iidm/TerminalTest.cpp b/test/iidm/TerminalTest.cpp index f26ef099..e46d5848 100644 --- a/test/iidm/TerminalTest.cpp +++ b/test/iidm/TerminalTest.cpp @@ -22,14 +22,14 @@ BOOST_AUTO_TEST_SUITE(TerminalTestSuite) class CustomTopologyTraverser : public VoltageLevel::TopologyTraverser { public: // VoltageLevel::TopologyTraverser - bool traverse(Terminal& terminal, bool /*connected*/) override { + math::TraverseResult traverse(Terminal& terminal, bool /*connected*/) override { m_traversedConnectables.insert(terminal.getConnectable().get().getId()); - return m_traverseTerminals; + return m_traverseTerminals ? math::TraverseResult::CONTINUE : math::TraverseResult::TERMINATE_PATH; } - bool traverse(Switch& aSwitch) override { + math::TraverseResult traverse(Switch& aSwitch) override { m_traversedSwitches.insert(aSwitch.getId()); - return m_traverseSwitches; + return m_traverseSwitches ? math::TraverseResult::CONTINUE : math::TraverseResult::TERMINATE_PATH; } public: diff --git a/test/iidm/TopologyTraverserTest.cpp b/test/iidm/TopologyTraverserTest.cpp new file mode 100644 index 00000000..071f991b --- /dev/null +++ b/test/iidm/TopologyTraverserTest.cpp @@ -0,0 +1,207 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * 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 http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "NetworkFactory.hpp" + +namespace std { + +std::ostream& operator<<(std::ostream& stream, const std::pair& ip) { + stream << ip.first << " " << ip.second; + return stream; +} + +} // namespace std + +namespace powsybl { + +namespace iidm { + +using IdPos = std::pair; + +using IdPosSet = std::set>; + +class CustomTopologyTraverser : public voltage_level::TopologyTraverser { +public: + using SwitchTest = std::function; + + using TerminalTest = std::function; + +public: // voltage_level::TopologyTraverser + math::TraverseResult traverse(Terminal& terminal, bool /*connected*/) override { + auto pair = m_visited.insert(terminal); + if (!pair.second) { + BOOST_FAIL("Traversing an already visited terminal"); + } + return m_terminalTest(terminal); + } + + math::TraverseResult traverse(Switch& aSwitch) override { + return m_switchTest(aSwitch); + } + +public: + CustomTopologyTraverser(TerminalSet& visited, const SwitchTest& switchTest, const TerminalTest& terminalTest) : + m_visited(visited), + m_switchTest(switchTest), + m_terminalTest(terminalTest) { + } + +private: + TerminalSet& m_visited; + + SwitchTest m_switchTest; + + TerminalTest m_terminalTest; +}; + +static int indexOfTerminal(const Terminal& terminal) { + const auto& allTerminals = terminal.getConnectable().get().getTerminals(); + auto it = std::find_if(allTerminals.begin(), allTerminals.end(), [&terminal](const std::reference_wrapper& term) { + return stdcxx::areSame(terminal, term.get()); + }); + return it != allTerminals.end() ? it - allTerminals.begin() : -1; +} + +IdPosSet getVisitedList(Terminal& start, const CustomTopologyTraverser::SwitchTest& switchTest, const CustomTopologyTraverser::TerminalTest& terminalTest) { + TerminalSet visited; + + CustomTopologyTraverser traverser(visited, switchTest, terminalTest); + start.traverse(traverser); + + IdPosSet visitedInfos; + std::transform(visited.begin(), visited.end(), std::inserter(visitedInfos,visitedInfos.begin()), [](const std::reference_wrapper& terminal) { + return std::make_pair(terminal.get().getConnectable().get().getId(), indexOfTerminal(terminal.get())); + }); + + return visitedInfos; +} + +IdPosSet getVisitedList(Terminal& start, const CustomTopologyTraverser::SwitchTest& switchTest) { + const CustomTopologyTraverser::TerminalTest& terminalTest = [](Terminal& /*Terminal*/) { + return math::TraverseResult::CONTINUE; + }; + return getVisitedList(start, switchTest, terminalTest); +} + +IdPosSet getVisitedList(Terminal& start) { + const CustomTopologyTraverser::SwitchTest& switchTest = [](Switch& /*sw*/) { + return math::TraverseResult::CONTINUE; + }; + return getVisitedList(start, switchTest); +} + +BOOST_AUTO_TEST_SUITE(TopologyTraverserTestSuite) + +BOOST_AUTO_TEST_CASE(test1) { + Network network = createNodeBreakerNetwork(); + Terminal& start = network.getGenerator("G").getTerminal(); + const auto& res = getVisitedList(start); + + IdPosSet expected = {{"G", 0}, {"BBS1", 0}, {"L1", 0}, {"L1", 1}, {"BBS2", 0}, {"LD", 0}}; + + BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), res.begin(), res.end()); +} + +BOOST_AUTO_TEST_CASE(test2) { + Network network = createNodeBreakerNetwork(); + Terminal& start = network.getVoltageLevel("VL1").getNodeBreakerView().getBusbarSection("BBS1").get().getTerminal(); + const auto& res = getVisitedList(start, [](Switch& sw) { + return (!sw.isOpen() && sw.getKind() != SwitchKind::BREAKER) ? math::TraverseResult::CONTINUE : math::TraverseResult::TERMINATE_PATH; + }); + + IdPosSet expected = { {"BBS1", 0}, {"G", 0} }; + + BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), res.begin(), res.end()); +} + +BOOST_AUTO_TEST_CASE(test3) { + Network network = createMixedNodeBreakerBusBreakerNetwork(); + Terminal& start = network.getGenerator("G").getTerminal(); + const auto& visited1 = getVisitedList(start); + IdPosSet expected1 = { {"BBS1", 0}, {"G", 0}, {"L1", 0}, + {"L1", 1}, {"BBS2", 0}, {"LD", 0}, {"L2", 0}, + {"L2", 1}, {"LD2", 0} }; + BOOST_CHECK_EQUAL_COLLECTIONS(expected1.begin(), expected1.end(), visited1.begin(), visited1.end()); + + const auto& visited2 = getVisitedList(start, [](Switch& /*sw*/) { return math::TraverseResult::CONTINUE; }, [](Terminal& terminal) { + return terminal.getConnectable().get().getId() == "L2" ? math::TraverseResult::TERMINATE_PATH : math::TraverseResult::CONTINUE; + }); + IdPosSet expected2 = { {"G", 0}, {"BBS1", 0}, {"L1", 0}, {"L1", 1}, + {"BBS2", 0}, {"LD", 0}, {"L2", 0} }; + BOOST_CHECK_EQUAL_COLLECTIONS(expected2.begin(), expected2.end(), visited2.begin(), visited2.end()); + + const auto& visited3 = getVisitedList(network.getLoad("LD2").getTerminal(), [](Switch& /*sw*/) { return math::TraverseResult::CONTINUE; }, [](Terminal& terminal) { + return terminal.getConnectable().get().getId() == "L2" ? math::TraverseResult::TERMINATE_PATH : math::TraverseResult::CONTINUE; + }); + IdPosSet expected3 = { {"LD2", 0}, {"L2", 1} }; + BOOST_CHECK_EQUAL_COLLECTIONS(expected3.begin(), expected3.end(), visited3.begin(), visited3.end()); +} + +BOOST_AUTO_TEST_CASE(test4) { + Network network = powsybl::network::EurostagFactory::createTutorial1Network(); + Terminal& start = network.getGenerator("GEN").getTerminal(); + const auto& res = getVisitedList(start); + + IdPosSet expected = { {"GEN", 0}, {"NGEN_NHV1", 0}, {"NGEN_NHV1", 1}, + {"NHV1_NHV2_1", 0}, {"NHV1_NHV2_2", 0}, {"NHV1_NHV2_1", 1}, + {"NHV1_NHV2_2", 1}, {"NHV2_NLOAD", 0}, {"NHV2_NLOAD", 1}, {"LOAD", 0} }; + + BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), res.begin(), res.end()); +} + +BOOST_AUTO_TEST_CASE(test5) { + Network network = powsybl::network::EurostagFactory::createTutorial1Network(); + // Duplicate 2wt to go from VLGEN to VLHV1 even if traverser stops at one of them + TwoWindingsTransformer& transformer = network.getTwoWindingsTransformer("NGEN_NHV1"); + TwoWindingsTransformer& duplicatedTransformer = network.getSubstation("P1") + .newTwoWindingsTransformer() + .setId("duplicate") + .setVoltageLevel1("VLGEN").setBus1("NGEN") + .setVoltageLevel2("VLHV1").setBus2("NHV1") + .setRatedU1(transformer.getRatedU1()) + .setRatedU2(transformer.getRatedU2()) + .setR(transformer.getR()) + .setX(transformer.getX()) + .setG(transformer.getG()) + .setB(transformer.getB()) + .add(); + + Terminal& start = network.getGenerator("GEN").getTerminal(); + const auto& res = getVisitedList(start, [](Switch& /*sw*/) { return math::TraverseResult::CONTINUE; }, [&duplicatedTransformer](Terminal& terminal) { + return (stdcxx::areSame(terminal.getConnectable().get(), duplicatedTransformer) && terminal.getVoltageLevel().getId() == "VLGEN") ? math::TraverseResult::TERMINATE_PATH : math::TraverseResult::CONTINUE; + }); + IdPosSet expected = { {"GEN", 0}, {"NGEN_NHV1", 0}, {"duplicate", 0}, {"NGEN_NHV1", 1}, + {"NHV1_NHV2_1", 0}, {"NHV1_NHV2_2", 0}, {"duplicate", 1}, {"NHV1_NHV2_1", 1}, + {"NHV1_NHV2_2", 1}, {"NHV2_NLOAD", 0}, {"NHV2_NLOAD", 1}, {"LOAD", 0} }; + + BOOST_CHECK_EQUAL_COLLECTIONS(expected.begin(), expected.end(), res.begin(), res.end()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace iidm + +} // namespace powsybl