diff --git a/include/powsybl/iidm/VoltageLevelViews.hpp b/include/powsybl/iidm/VoltageLevelViews.hpp index 84b7ccce7..fee2bd382 100644 --- a/include/powsybl/iidm/VoltageLevelViews.hpp +++ b/include/powsybl/iidm/VoltageLevelViews.hpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include @@ -35,6 +37,8 @@ class BusBreakerView { public: using SwitchAdder = bus_breaker_view::SwitchAdder; + using Traverser = std::function& sw, const Bus& bus2)>; + public: virtual ~BusBreakerView() noexcept = default; @@ -75,6 +79,8 @@ class BusBreakerView { virtual void removeBus(const std::string& busId) = 0; virtual void removeSwitch(const std::string& switchId) = 0; + + virtual void traverse(const Bus& bus, Traverser& traverser) = 0; }; class BusView { @@ -102,7 +108,7 @@ class NodeBreakerView { using SwitchAdder = node_breaker_view::SwitchAdder; - using Traverser = std::function& sw, unsigned long node2)>; + using Traverser = std::function& sw, unsigned long node2)>; public: virtual ~NodeBreakerView() noexcept = default; @@ -172,6 +178,8 @@ class NodeBreakerView { virtual void removeSwitch(const std::string& switchId) = 0; virtual void traverse(unsigned long node, const Traverser& traverser) const = 0; + + virtual void traverse(stdcxx::const_range& nodes, const Traverser& traverser) const = 0; }; } // namespace voltage_level diff --git a/include/powsybl/iidm/util/NodeBreakerTopology.hpp b/include/powsybl/iidm/util/NodeBreakerTopology.hpp new file mode 100644 index 000000000..e5a6e45c6 --- /dev/null +++ b/include/powsybl/iidm/util/NodeBreakerTopology.hpp @@ -0,0 +1,29 @@ +/** + * 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/. + */ + +#ifndef POWSYBL_IIDM_UTIL_NODEBREAKERTOPOLOGY_HPP +#define POWSYBL_IIDM_UTIL_NODEBREAKERTOPOLOGY_HPP + +#include + +namespace powsybl { + +namespace iidm { + +namespace NodeBreakerTopology { + +unsigned long newStandardConnection(BusbarSection& bb); + +void removeIsolatedSwitches(voltage_level::NodeBreakerView& topo); + +} // namespace NodeBreakerTopology + +} // namespace iidm + +} // namespace powsybl + +#endif // POWSYBL_IIDM_UTIL_NODEBREAKERTOPOLOGY_HPP diff --git a/include/powsybl/math/TraverseResult.hpp b/include/powsybl/math/TraverseResult.hpp index 65f55ad34..9ae4af2b8 100644 --- a/include/powsybl/math/TraverseResult.hpp +++ b/include/powsybl/math/TraverseResult.hpp @@ -13,8 +13,14 @@ namespace powsybl { namespace math { enum class TraverseResult { + /** Indicates that traversal should continue */ CONTINUE, - TERMINATE + + /** Indicates that traversal should terminate on current path */ + TERMINATE_PATH, + + /** Indicates that traversal should break, i.e., terminate on all paths */ + TERMINATE_TRAVERSER }; } // namespace math diff --git a/include/powsybl/math/UndirectedGraph.hpp b/include/powsybl/math/UndirectedGraph.hpp index e80ba56a4..5c9f31ecf 100644 --- a/include/powsybl/math/UndirectedGraph.hpp +++ b/include/powsybl/math/UndirectedGraph.hpp @@ -92,9 +92,11 @@ class UndirectedGraph { void setVertexObject(unsigned long v, const stdcxx::Reference& object); - void traverse(unsigned long v, const Traverser& traverser) const; + bool traverse(unsigned long v, const Traverser& traverser) const; - void traverse(unsigned long v, const Traverser& traverser, std::vector& encountered) const; + bool traverse(stdcxx::const_range& startingVertices, const Traverser& traverser) const; + + bool traverse(unsigned long v, const Traverser& traverser, std::vector& encountered) const; bool vertexExists(unsigned long v) const; diff --git a/include/powsybl/math/UndirectedGraph.hxx b/include/powsybl/math/UndirectedGraph.hxx index 90746fdc9..9aac1fe13 100644 --- a/include/powsybl/math/UndirectedGraph.hxx +++ b/include/powsybl/math/UndirectedGraph.hxx @@ -418,14 +418,28 @@ void UndirectedGraph::setVertexObject(unsigned long v, const stdcxx::Refer } template -void UndirectedGraph::traverse(unsigned long v, const Traverser& traverser) const { +bool UndirectedGraph::traverse(unsigned long v, const Traverser& traverser) const { std::vector encountered(m_vertices.size(), false); - traverse(v, traverser, encountered); + return traverse(v, traverser, encountered); } template -void UndirectedGraph::traverse(unsigned long v, const Traverser& traverser, std::vector& encountered) const { +bool UndirectedGraph::traverse(stdcxx::const_range& startingVertices, const Traverser& traverser) const { + std::vector encountered(m_vertices.size(), false); + + for (unsigned long startingVertex : startingVertices) { + if (!encountered[startingVertex]) { + if (!traverse(startingVertex, traverser, encountered)) { + return false; + } + } + } + return true; +} + +template +bool UndirectedGraph::traverse(unsigned long v, const Traverser& traverser, std::vector& encountered) const { checkVertex(v); encountered.resize(m_vertices.size(), false); @@ -434,20 +448,34 @@ void UndirectedGraph::traverse(unsigned long v, const Traverser& traverser const std::vector& adjacentEdges = adjacencyList[v]; encountered[v] = true; + bool keepGoing = true; for (unsigned long e : adjacentEdges) { const std::unique_ptr& edge = m_edges[e]; unsigned long v1 = edge->getVertex1(); unsigned long v2 = edge->getVertex2(); if (!encountered[v1]) { - if (traverser(v2, e, v1) == TraverseResult::CONTINUE) { + const TraverseResult& traverserResult = traverser(v2, e, v1); + if (traverserResult == TraverseResult::CONTINUE) { encountered[v1] = true; - traverse(v1, traverser, encountered); + keepGoing = traverse(v1, traverser, encountered); + } else if (traverserResult == TraverseResult::TERMINATE_TRAVERSER) { + keepGoing = false; + } + } else if (!encountered[v2]) { + const TraverseResult& traverserResult = traverser(v1, e, v2); + if (traverserResult == TraverseResult::CONTINUE) { + encountered[v2] = true; + keepGoing = traverse(v2, traverser, encountered); + } else if (traverserResult == TraverseResult::TERMINATE_TRAVERSER) { + keepGoing = false; } - } else if (!encountered[v2] && (traverser(v1, e, v2) == TraverseResult::CONTINUE)) { - encountered[v2] = true; - traverse(v2, traverser, encountered); + } + if (!keepGoing) { + break; } } + + return keepGoing; } template diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 49d078892..c88bfc109 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -205,6 +205,7 @@ set(IIDM_SOURCES iidm/util/LimitViolationUtils.cpp iidm/util/LinkData.cpp iidm/util/Networks.cpp + iidm/util/NodeBreakerTopology.cpp iidm/util/Substations.cpp iidm/util/SV.cpp iidm/util/TerminalFinder.cpp diff --git a/src/iidm/BusBreakerVoltageLevel.cpp b/src/iidm/BusBreakerVoltageLevel.cpp index 952b8ca2c..21890d4b2 100644 --- a/src/iidm/BusBreakerVoltageLevel.cpp +++ b/src/iidm/BusBreakerVoltageLevel.cpp @@ -405,7 +405,7 @@ void BusBreakerVoltageLevel::traverse(BusTerminal& terminal, VoltageLevel::Topol return math::TraverseResult::CONTINUE; } } - return math::TraverseResult::TERMINATE; + return math::TraverseResult::TERMINATE_PATH; }); for (Terminal& t : nextTerminals) { diff --git a/src/iidm/BusBreakerVoltageLevelTopology.cpp b/src/iidm/BusBreakerVoltageLevelTopology.cpp index debc17e07..3a3c0b24d 100644 --- a/src/iidm/BusBreakerVoltageLevelTopology.cpp +++ b/src/iidm/BusBreakerVoltageLevelTopology.cpp @@ -120,7 +120,7 @@ void CalculatedBusTopology::updateCache() { graph.traverse(v, [&busSet, &graph](unsigned long /*v1*/, unsigned long e, unsigned long v2) { stdcxx::Reference aSwitch = graph.getEdgeObject(e); if (aSwitch.get().isOpen()) { - return math::TraverseResult::TERMINATE; + return math::TraverseResult::TERMINATE_PATH; } busSet.push_back(std::ref(graph.getVertexObject(v2).get())); diff --git a/src/iidm/BusBreakerVoltageLevelViews.cpp b/src/iidm/BusBreakerVoltageLevelViews.cpp index 668eaac90..996378190 100644 --- a/src/iidm/BusBreakerVoltageLevelViews.cpp +++ b/src/iidm/BusBreakerVoltageLevelViews.cpp @@ -125,6 +125,15 @@ void BusBreakerViewImpl::removeSwitch(const std::string& switchId) { m_voltageLevel.removeSwitch(switchId); } +void BusBreakerViewImpl::traverse(const Bus& bus, Traverser& traverser) { + math::Traverser graphTraverser = [this, &traverser](unsigned long v1, unsigned long e, unsigned long v2) { + const auto& graph = m_voltageLevel.getGraph(); + return traverser(graph.getVertexObject(v1), graph.getEdgeObject(e), graph.getVertexObject(v2)); + }; + + m_voltageLevel.getGraph().traverse(*m_voltageLevel.getVertex(bus.getId(), true), graphTraverser); +} + BusViewImpl::BusViewImpl(BusBreakerVoltageLevel& voltageLevel) : m_voltageLevel(voltageLevel) { } diff --git a/src/iidm/BusBreakerVoltageLevelViews.hpp b/src/iidm/BusBreakerVoltageLevelViews.hpp index f393a9669..0a9a82b9d 100644 --- a/src/iidm/BusBreakerVoltageLevelViews.hpp +++ b/src/iidm/BusBreakerVoltageLevelViews.hpp @@ -59,6 +59,8 @@ class BusBreakerViewImpl : public voltage_level::BusBreakerView { void removeSwitch(const std::string& switchId) override; + void traverse(const Bus& bus, Traverser& traverser) override; + public: explicit BusBreakerViewImpl(BusBreakerVoltageLevel& voltageLevel); diff --git a/src/iidm/CalculatedBus.cpp b/src/iidm/CalculatedBus.cpp index 30652f323..c0327c82a 100644 --- a/src/iidm/CalculatedBus.cpp +++ b/src/iidm/CalculatedBus.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -38,23 +39,7 @@ stdcxx::CReference CalculatedBus::findTerminal(const NodeBreakerVo if (!terminals.empty()) { return stdcxx::cref(terminals.front().get()); } - - stdcxx::CReference terminal; - - VoltageLevel::NodeBreakerView::Traverser traverser = [&terminal, &voltageLevel](unsigned long /*node1*/, const stdcxx::Reference& sw, unsigned long node2) { - if (terminal) { - return false; - } - - if (static_cast(sw) && sw.get().isOpen()) { - return false; - } - terminal = voltageLevel.getNodeBreakerView().getTerminal(node2); - return !static_cast(terminal); - }; - voltageLevel.getNodeBreakerView().traverse(nodes.front(), traverser); - - return stdcxx::cref(terminal); + return stdcxx::cref(Networks::getEquivalentTerminal(voltageLevel, nodes.front())); } double CalculatedBus::getAngle() const { diff --git a/src/iidm/NodeBreakerVoltageLevel.cpp b/src/iidm/NodeBreakerVoltageLevel.cpp index bb39013f5..50e1555c0 100644 --- a/src/iidm/NodeBreakerVoltageLevel.cpp +++ b/src/iidm/NodeBreakerVoltageLevel.cpp @@ -422,9 +422,9 @@ void NodeBreakerVoltageLevel::traverse(NodeTerminal& terminal, VoltageLevel::Top addNextTerminals(otherTerminal.get(), nextTerminals); return math::TraverseResult::CONTINUE; } - return math::TraverseResult::TERMINATE; + return math::TraverseResult::TERMINATE_PATH; } - return math::TraverseResult::TERMINATE; + return math::TraverseResult::TERMINATE_PATH; }); for (auto nextTerminal : nextTerminals) { diff --git a/src/iidm/NodeBreakerVoltageLevelTopology.cpp b/src/iidm/NodeBreakerVoltageLevelTopology.cpp index abfc9b196..c54d85d8b 100644 --- a/src/iidm/NodeBreakerVoltageLevelTopology.cpp +++ b/src/iidm/NodeBreakerVoltageLevelTopology.cpp @@ -168,11 +168,11 @@ stdcxx::Reference CalculatedBusTopology::getConnectableBus(unsigned long no if (static_cast(connectableBus)) { // traverse does not stop the algorithm when TERMINATE, it only stops searching in a given direction // this condition insures that while checking all the edges (in every direction) of a node, if a bus is found, it will not be lost - return math::TraverseResult::TERMINATE; + return math::TraverseResult::TERMINATE_PATH; } connectableBus = getBus(v2); - return static_cast(connectableBus) ? math::TraverseResult::TERMINATE : math::TraverseResult::CONTINUE; + return static_cast(connectableBus) ? math::TraverseResult::TERMINATE_PATH : math::TraverseResult::CONTINUE; }); // if nothing found, just take the first bus @@ -255,7 +255,7 @@ void CalculatedBusTopology::traverse(unsigned long v, std::vector& encount graph.traverse(v, [&graph, &terminate, &vertices](unsigned long /*v1*/, unsigned long e, unsigned long v2) { const stdcxx::Reference aSwitch = graph.getEdgeObject(e); if (static_cast(aSwitch) && terminate(aSwitch)) { - return math::TraverseResult::TERMINATE; + return math::TraverseResult::TERMINATE_PATH; } vertices.push_back(v2); diff --git a/src/iidm/NodeBreakerVoltageLevelViews.cpp b/src/iidm/NodeBreakerVoltageLevelViews.cpp index e43dd82fb..c239d9b5b 100644 --- a/src/iidm/NodeBreakerVoltageLevelViews.cpp +++ b/src/iidm/NodeBreakerVoltageLevelViews.cpp @@ -109,6 +109,10 @@ void BusBreakerViewImpl::removeSwitch(const std::string& /*switchId*/) { throw AssertionError("Not implemented"); } +void BusBreakerViewImpl::traverse(const Bus& /*bus*/, Traverser& /*traverser*/) { + throw AssertionError("Not implemented"); +} + BusViewImpl::BusViewImpl(NodeBreakerVoltageLevel& voltageLevel) : m_voltageLevel(voltageLevel) { } @@ -312,13 +316,21 @@ void NodeBreakerViewImpl::removeSwitch(const std::string& switchId) { } void NodeBreakerViewImpl::traverse(unsigned long node, const Traverser& traverser) const { - powsybl::math::Traverser graphTraverser = [this, &traverser](unsigned long v1, unsigned long e, unsigned long v2) { - return traverser(v1, m_voltageLevel.getGraph().getEdgeObject(e), v2) ? powsybl::math::TraverseResult::CONTINUE : powsybl::math::TraverseResult::TERMINATE; + math::Traverser graphTraverser = [this, &traverser](unsigned long v1, unsigned long e, unsigned long v2) { + return traverser(v1, m_voltageLevel.getGraph().getEdgeObject(e), v2); }; m_voltageLevel.getGraph().traverse(node, graphTraverser); } +void NodeBreakerViewImpl::traverse(stdcxx::const_range& nodes, const Traverser& traverser) const { + powsybl::math::Traverser graphTraverser = [this, &traverser](unsigned long v1, unsigned long e, unsigned long v2) { + return traverser(v1, m_voltageLevel.getGraph().getEdgeObject(e), v2); + }; + + m_voltageLevel.getGraph().traverse(nodes, graphTraverser); +} + } // namespace node_breaker_voltage_level } // namespace iidm diff --git a/src/iidm/NodeBreakerVoltageLevelViews.hpp b/src/iidm/NodeBreakerVoltageLevelViews.hpp index 070051d78..d69a07c94 100644 --- a/src/iidm/NodeBreakerVoltageLevelViews.hpp +++ b/src/iidm/NodeBreakerVoltageLevelViews.hpp @@ -84,6 +84,8 @@ class NodeBreakerViewImpl : public voltage_level::NodeBreakerView { void traverse(unsigned long node, const Traverser& traverser) const override; + void traverse(stdcxx::const_range& nodes, const Traverser& traverser) const override; + public: explicit NodeBreakerViewImpl(NodeBreakerVoltageLevel& voltageLevel); @@ -121,6 +123,8 @@ class BusBreakerViewImpl : public voltage_level::BusBreakerView { unsigned long getSwitchCount() const override; + void traverse(const Bus& bus, Traverser& traverser) override; + public: explicit BusBreakerViewImpl(NodeBreakerVoltageLevel& voltageLevel); diff --git a/src/iidm/util/Networks.cpp b/src/iidm/util/Networks.cpp index df42d0278..f29ffc3d8 100644 --- a/src/iidm/util/Networks.cpp +++ b/src/iidm/util/Networks.cpp @@ -28,13 +28,13 @@ stdcxx::CReference getEquivalentTerminal(const VoltageLevel& voltageLe VoltageLevel::NodeBreakerView::Traverser traverser = [&equivalentTerminal, &voltageLevel](unsigned long /*node1*/, const stdcxx::Reference& sw, unsigned long node2) { if (sw && sw.get().isOpen()) { - return false; + return math::TraverseResult::TERMINATE_PATH; } const auto& terminal = voltageLevel.getNodeBreakerView().getTerminal(node2); if (terminal) { equivalentTerminal = terminal; } - return !terminal; + return terminal ? math::TraverseResult::TERMINATE_TRAVERSER : math::TraverseResult::CONTINUE; }; voltageLevel.getNodeBreakerView().traverse(node, traverser); diff --git a/src/iidm/util/NodeBreakerTopology.cpp b/src/iidm/util/NodeBreakerTopology.cpp new file mode 100644 index 000000000..c282f2b2e --- /dev/null +++ b/src/iidm/util/NodeBreakerTopology.cpp @@ -0,0 +1,74 @@ +/** + * 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 + +namespace powsybl { + +namespace iidm { + +namespace NodeBreakerTopology { + +unsigned long newStandardConnection(BusbarSection& bb) { + unsigned long n = bb.getTerminal().getNodeBreakerView().getNode(); + VoltageLevel::NodeBreakerView& topo = bb.getTerminal().getVoltageLevel().getNodeBreakerView(); + + const long& oldCount = topo.getMaximumNodeIndex() + 1L; + topo.newDisconnector() + .setId(stdcxx::format("disconnector %1%-%2%", bb.getId(), oldCount)) + .setNode1(n) + .setNode2(oldCount) + .add(); + topo.newBreaker() + .setId(stdcxx::format("breaker %1%-%2%", bb.getId(), oldCount + 1)) + .setNode1(oldCount) + .setNode2(oldCount + 1) + .add(); + + return oldCount + 1; +} + +void removeIsolatedSwitches(voltage_level::NodeBreakerView& topo) { + const auto& filter = [&topo](unsigned int node) { + return static_cast(topo.getTerminal(node)); + }; + stdcxx::const_range nodesWithTerminal = topo.getNodes() | boost::adaptors::filtered(filter); + + std::vector> encounteredSwitches; + voltage_level::NodeBreakerView::Traverser traverser = [&encounteredSwitches](unsigned long /*node1*/, const stdcxx::Reference& sw, unsigned long /*node2*/) { + encounteredSwitches.emplace_back(std::ref(sw.get())); // the traversing started from a terminal, thus the switch is not isolated + return math::TraverseResult::CONTINUE; // if n2 has a terminal, we could also choose to stop as it will be or has already been traversed + }; + topo.traverse(nodesWithTerminal, traverser); + + const auto& filterNotEncounteredSwitches = [&encounteredSwitches](const Switch& sw) { + auto it = std::find_if(encounteredSwitches.begin(), encounteredSwitches.end(), [&sw](const std::reference_wrapper& swInList) { + return stdcxx::areSame(sw, swInList.get()); + }); + return it == encounteredSwitches.end(); + }; + for (const Switch& sw : topo.getSwitches() | boost::adaptors::filtered(filterNotEncounteredSwitches)) { + topo.removeSwitch(sw.getId()); + } +} + +} // namespace NodeBreakerTopology + +} // namespace iidm + +} // namespace powsybl diff --git a/test/iidm/CMakeLists.txt b/test/iidm/CMakeLists.txt index 38774e8d2..e492b8f05 100644 --- a/test/iidm/CMakeLists.txt +++ b/test/iidm/CMakeLists.txt @@ -59,6 +59,7 @@ set(UNIT_TEST_SOURCES extensions/SlackTerminalTest.cpp util/SVTest.cpp + util/NodeBreakerTopologyTest.cpp util/TerminalFinderTest.cpp ) diff --git a/test/iidm/NetworkFactory.cpp b/test/iidm/NetworkFactory.cpp index e3b3b55a9..ddabbe156 100644 --- a/test/iidm/NetworkFactory.cpp +++ b/test/iidm/NetworkFactory.cpp @@ -8,6 +8,9 @@ #include "NetworkFactory.hpp" #include +#include +#include +#include #include #include #include @@ -121,6 +124,82 @@ Network createNetwork() { return network; } +Network createNetworkTest1() { + Network network("network", "test"); + Substation& substation1 = network.newSubstation() + .setId("substation1") + .setCountry(Country::FR) + .setTso("TSO1") + .setGeographicalTags({"region1"}) + .add(); + VoltageLevel& voltageLevel1 = substation1.newVoltageLevel() + .setId("voltageLevel1") + .setNominalV(400) + .setTopologyKind(TopologyKind::NODE_BREAKER) + .add(); + VoltageLevel::NodeBreakerView& topology1 = voltageLevel1.getNodeBreakerView(); + BusbarSection& voltageLevel1BusbarSection1 = topology1.newBusbarSection() + .setId("voltageLevel1BusbarSection1") + .setNode(0) + .add(); + BusbarSection& voltageLevel1BusbarSection2 = topology1.newBusbarSection() + .setId("voltageLevel1BusbarSection2") + .setNode(1) + .add(); + topology1.newBreaker() + .setId("voltageLevel1Breaker1") + .setRetained(true) + .setOpen(false) + .setNode1(voltageLevel1BusbarSection1.getTerminal().getNodeBreakerView().getNode()) + .setNode2(voltageLevel1BusbarSection2.getTerminal().getNodeBreakerView().getNode()) + .add(); + Load& load1 = voltageLevel1.newLoad() + .setId("load1") + .setNode(2) + .setP0(10) + .setQ0(3) + .add(); + topology1.newDisconnector() + .setId("load1Disconnector1") + .setOpen(false) + .setNode1(load1.getTerminal().getNodeBreakerView().getNode()) + .setNode2(3) + .add(); + topology1.newDisconnector() + .setId("load1Breaker1") + .setOpen(false) + .setNode1(3) + .setNode2(voltageLevel1BusbarSection1.getTerminal().getNodeBreakerView().getNode()) + .add(); + Generator& generator1 = voltageLevel1.newGenerator() + .setId("generator1") + .setEnergySource(EnergySource::NUCLEAR) + .setMinP(200.0) + .setMaxP(900.0) + .setVoltageRegulatorOn(true) + .setTargetP(900.0) + .setTargetV(380.0) + .setNode(5) + .add(); + generator1.newReactiveCapabilityCurve() + .beginPoint().setP(200.0).setMinQ(300.0).setMaxQ(500.0).endPoint() + .beginPoint().setP(900.0).setMinQ(300.0).setMaxQ(500.0).endPoint() + .add(); + topology1.newDisconnector() + .setId("generator1Disconnector1") + .setOpen(false) + .setNode1(generator1.getTerminal().getNodeBreakerView().getNode()) + .setNode2(6) + .add(); + topology1.newDisconnector() + .setId("generator1Breaker1") + .setOpen(false) + .setNode1(6) + .setNode2(voltageLevel1BusbarSection2.getTerminal().getNodeBreakerView().getNode()) + .add(); + return network; +} + Terminal& getTerminalFromNetwork2() { if(network2.getSubstationCount() == 0) { Substation& s = network2.newSubstation() diff --git a/test/iidm/NetworkFactory.hpp b/test/iidm/NetworkFactory.hpp index 967b5239b..3a99a21f3 100644 --- a/test/iidm/NetworkFactory.hpp +++ b/test/iidm/NetworkFactory.hpp @@ -23,6 +23,8 @@ Network createHvdcConverterStationTestNetwork(); Network createNetwork(); +Network createNetworkTest1(); + Terminal& getTerminalFromNetwork2(); Network createDanglingLineNetwork(); diff --git a/test/iidm/NodeBreakerVoltageLevelTest.cpp b/test/iidm/NodeBreakerVoltageLevelTest.cpp index 026c234f7..a7d7bf469 100644 --- a/test/iidm/NodeBreakerVoltageLevelTest.cpp +++ b/test/iidm/NodeBreakerVoltageLevelTest.cpp @@ -615,7 +615,7 @@ BOOST_AUTO_TEST_CASE(NodeBreakerViewTest) { POWSYBL_ASSERT_THROW(voltageLevel.getNodeBreakerView().getNode1("UNKNOWN"), PowsyblException, "Switch 'UNKNOWN' not found in the voltage level 'VL2'"); VoltageLevel::NodeBreakerView::Traverser traverser = [=](unsigned long /*node1*/, const stdcxx::Reference& /*sw*/, unsigned long node2) { - return (node2 < (NODE_COUNT - 1)); + return (node2 < (NODE_COUNT - 1)) ? math::TraverseResult::CONTINUE : math::TraverseResult::TERMINATE_TRAVERSER; }; voltageLevel.getNodeBreakerView().traverse(0, traverser); diff --git a/test/iidm/util/NodeBreakerTopologyTest.cpp b/test/iidm/util/NodeBreakerTopologyTest.cpp new file mode 100644 index 000000000..2d183bb26 --- /dev/null +++ b/test/iidm/util/NodeBreakerTopologyTest.cpp @@ -0,0 +1,79 @@ +/** + * 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 "../NetworkFactory.hpp" + +namespace powsybl { + +namespace iidm { + +BOOST_AUTO_TEST_SUITE(NodeBreakerTopologyTestSuite) + +BOOST_AUTO_TEST_CASE(removeIsolatedSwitches) { + Network network = createNetworkTest1(); + + VoltageLevel& vl = network.getVoltageLevel("voltageLevel1"); + VoltageLevel::NodeBreakerView& topo = vl.getNodeBreakerView(); + + BOOST_CHECK(topo.getSwitch("load1Disconnector1")); + BOOST_CHECK(topo.getSwitch("load1Breaker1")); + BOOST_CHECK_EQUAL(5, topo.getSwitchCount()); + + // remove the load + vl.getConnectable("load1").get().remove(); + BOOST_CHECK(!vl.getConnectable("load1")); + + // remove the switch connected to the bus bar + topo.removeSwitch("load1Breaker1"); + BOOST_CHECK(!topo.getSwitch("load1Breaker1")); + + // The connecting switch of the load is now isolated: remove it + NodeBreakerTopology::removeIsolatedSwitches(topo); + + BOOST_CHECK(!topo.getSwitch("load1Disconnector1")); + BOOST_CHECK(!topo.getSwitch("load1Breaker1")); + // 2 switches have been removed + BOOST_CHECK_EQUAL(3, topo.getSwitchCount()); +} + +BOOST_AUTO_TEST_CASE(newStandardConnection) { + Network network = createNetworkTest1(); + + VoltageLevel& vl = network.getVoltageLevel("voltageLevel1"); + VoltageLevel::NodeBreakerView& topo = vl.getNodeBreakerView(); + + unsigned long initialSwitchCount = topo.getSwitchCount(); + + BusbarSection& bb = topo.getBusbarSection("voltageLevel1BusbarSection1"); + unsigned long connectionNode = NodeBreakerTopology::newStandardConnection(bb); + + const Load& load = vl.newLoad() + .setId("load2") + .setP0(10) + .setQ0(0) + .setNode(connectionNode) + .add(); + + // Check the new load is correctly connected to the bus corresponding to the bus bar. + BOOST_CHECK(stdcxx::areSame(bb.getTerminal().getBusView().getBus().get(), load.getTerminal().getBusView().getBus().get())); + BOOST_CHECK_EQUAL(initialSwitchCount + 2, topo.getSwitchCount()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace iidm + +} // namespace powsybl diff --git a/test/math/UndirectedGraphTest.cpp b/test/math/UndirectedGraphTest.cpp index 02c78c13b..fe6ef28e4 100644 --- a/test/math/UndirectedGraphTest.cpp +++ b/test/math/UndirectedGraphTest.cpp @@ -308,15 +308,44 @@ BOOST_AUTO_TEST_CASE(traverse) { graph.addEdge(4, 5, stdcxx::ref()); graph.addEdge(3, 5, stdcxx::ref()); - Traverser traverser = [](unsigned long /*v1*/, unsigned long e, unsigned long /*v2*/) { - return (e == 3 || e == 4 || e == 6) ? TraverseResult::TERMINATE : TraverseResult::CONTINUE; + const Traverser& traverser = [](unsigned long v1, unsigned long e, unsigned long v2) { + if (v1 == 4 && e == 3 && v2 == 1) { + return TraverseResult::TERMINATE_PATH; + } + if (v1 == 4 && e == 4 && v2 == 2) { + return TraverseResult::TERMINATE_PATH; + } + if (v1 == 5 && e == 6 && v2 == 3) { + return TraverseResult::TERMINATE_PATH; + } + return TraverseResult::CONTINUE; }; - std::vector encountered(graph.getVertexCount(), false); + std::vector encountered(graph.getVertexCount()); + std::fill(encountered.begin(), encountered.end(), false); + std::vector encounteredExpected = {false, false, false, false, true, true}; graph.traverse(5, traverser, encountered); - graph.traverse(5, traverser); + BOOST_CHECK_EQUAL_COLLECTIONS(encountered.begin(), encountered.end(), encounteredExpected.begin(), encounteredExpected.end()); - BOOST_CHECK_EQUAL_COLLECTIONS(expected.cbegin(), expected.cend(), encountered.cbegin(), encountered.cend()); + std::fill(encountered.begin(), encountered.end(), false); + const Traverser& traverser2 = [&](unsigned long v1, unsigned long /*e*/, unsigned long v2) { + encountered[v1] = true; + return v2 == 1 || v2 == 2 || v2 == 3 ? TraverseResult::TERMINATE_PATH : TraverseResult::CONTINUE; + }; + graph.traverse(4, traverser2); + // Only vertex 4 and 5 encountered + std::vector encounteredExpected2 = {false, false, false, false, true, true}; + BOOST_CHECK_EQUAL_COLLECTIONS(encountered.begin(), encountered.end(), encounteredExpected2.begin(), encounteredExpected2.end()); + + const Traverser& traverser3 = [&](unsigned long v1, unsigned long /*e*/, unsigned long v2) { + encountered[v1] = true; + return v2 == 0 ? TraverseResult::TERMINATE_TRAVERSER : TraverseResult::CONTINUE; + }; + std::fill(encountered.begin(), encountered.end(), false); + graph.traverse(5, traverser3, encountered); + // Only vertices on first path encountering 0 are encountered + std::vector encounteredExpected3 = {false, true, false, false, true, true}; + BOOST_CHECK_EQUAL_COLLECTIONS(encountered.begin(), encountered.end(), encounteredExpected3.begin(), encounteredExpected3.end()); } BOOST_AUTO_TEST_SUITE_END()