From 0111e0d27725b40171b320ba92c169642ae684fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Laigre?= Date: Mon, 31 May 2021 21:28:45 +0200 Subject: [PATCH] Fix multiple connect/disconnect in NodeBreakerVoltageLevel to avoid unwanted topology change (#285) (#324) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sébastien LAIGRE --- src/iidm/NodeBreakerVoltageLevel.cpp | 8 ++ test/iidm/CMakeLists.txt | 1 + test/iidm/NodeBreakerConnectTest.cpp | 182 +++++++++++++++++++++++++++ 3 files changed, 191 insertions(+) create mode 100644 test/iidm/NodeBreakerConnectTest.cpp diff --git a/src/iidm/NodeBreakerVoltageLevel.cpp b/src/iidm/NodeBreakerVoltageLevel.cpp index 85521c91..39089704 100644 --- a/src/iidm/NodeBreakerVoltageLevel.cpp +++ b/src/iidm/NodeBreakerVoltageLevel.cpp @@ -90,6 +90,10 @@ void NodeBreakerVoltageLevel::clean() { bool NodeBreakerVoltageLevel::connect(Terminal& terminal) { auto& nodeTerminal = dynamic_cast(terminal); + if (terminal.isConnected()) { + return false; + } + unsigned long node = nodeTerminal.getNode(); // find all paths starting from the current terminal to a busbar section that does not contain an open disconnector @@ -141,6 +145,10 @@ void NodeBreakerVoltageLevel::detach(Terminal& terminal) { bool NodeBreakerVoltageLevel::disconnect(Terminal& terminal) { auto& nodeTerminal = dynamic_cast(terminal); + if (!terminal.isConnected()) { + return false; + } + unsigned long node = nodeTerminal.getNode(); // find all paths starting from the current terminal to a busbar section that does not contain an open disconnector diff --git a/test/iidm/CMakeLists.txt b/test/iidm/CMakeLists.txt index 0a031e9b..8afb0cb1 100644 --- a/test/iidm/CMakeLists.txt +++ b/test/iidm/CMakeLists.txt @@ -29,6 +29,7 @@ set(UNIT_TEST_SOURCES NetworkFactory.cpp NetworkIndexTest.cpp NetworkTest.cpp + NodeBreakerConnectTest.cpp NodeBreakerVoltageLevelTest.cpp PhaseTapChangerTest.cpp RatioTapChangerTest.cpp diff --git a/test/iidm/NodeBreakerConnectTest.cpp b/test/iidm/NodeBreakerConnectTest.cpp new file mode 100644 index 00000000..56404995 --- /dev/null +++ b/test/iidm/NodeBreakerConnectTest.cpp @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2021, 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 + +namespace powsybl { + +namespace iidm { + +/** + *
+ *           LD        G
+ *           |    B1   |
+ *           |---[+]---|
+ *       B2 [-]       [+] B3
+ *           |    C    |
+ *  BBS1 --------[-]-------- BBS2
+ * 
+ */ +Network createNbkNetwork() { + Network network("test", "test"); + Substation& s = network.newSubstation() + .setId("S") + .setCountry(Country::FR) + .add(); + VoltageLevel& vl = s.newVoltageLevel() + .setId("VL") + .setNominalV(400.0) + .setTopologyKind(TopologyKind::NODE_BREAKER) + .add(); + vl.getNodeBreakerView().newBusbarSection() + .setId("BBS1") + .setNode(0) + .add(); + vl.getNodeBreakerView().newBusbarSection() + .setId("BBS2") + .setNode(1) + .add(); + vl.getNodeBreakerView().newBreaker() + .setId("C") + .setNode1(1) + .setNode2(0) + .setOpen(true) + .add(); + vl.getNodeBreakerView().newBreaker() + .setId("B2") + .setNode1(0) + .setNode2(2) + .setOpen(true) + .add(); + vl.getNodeBreakerView().newBreaker() + .setId("B1") + .setNode1(2) + .setNode2(3) + .setOpen(false) + .add(); + vl.getNodeBreakerView().newBreaker() + .setId("B3") + .setNode1(3) + .setNode2(1) + .setOpen(false) + .add(); + vl.newLoad() + .setId("LD") + .setNode(2) + .setP0(1) + .setQ0(1) + .add(); + vl.newGenerator() + .setId("G") + .setNode(3) + .setMinP(-9999.99) + .setMaxP(9999.99) + .setVoltageRegulatorOn(true) + .setTargetV(400) + .setTargetP(1) + .setTargetQ(0) + .add(); + return network; +} + +/** + *
+ *     L
+ *     |
+ *  ---1---
+ *  |     |
+ * BR1   BR2
+ *  |     |
+ *  ---0--- BBS1
+ * 
+ */ +Network createDiamondNetwork() { + Network network("test", "test"); + Substation& s = network.newSubstation() + .setId("S") + .setCountry(Country::FR) + .add(); + VoltageLevel& vl = s.newVoltageLevel() + .setId("VL") + .setNominalV(400.0) + .setTopologyKind(TopologyKind::NODE_BREAKER) + .add(); + vl.getNodeBreakerView().newBusbarSection() + .setId("BBS1") + .setNode(0) + .add(); + vl.newLoad() + .setId("L") + .setNode(1) + .setP0(1) + .setQ0(1) + .add(); + vl.getNodeBreakerView().newBreaker() + .setId("BR1") + .setNode1(1) + .setNode2(0) + .setOpen(false) + .add(); + vl.getNodeBreakerView().newBreaker() + .setId("BR2") + .setNode1(1) + .setNode2(0) + .setOpen(false) + .add(); + return network; +} + +BOOST_AUTO_TEST_SUITE(NetworkTestSuite) + +BOOST_AUTO_TEST_CASE(NodeBreakerConnectConnectedLoad) { + Network network = createNbkNetwork(); + Load& l = network.getLoad("LD"); + BOOST_CHECK(l.getTerminal().isConnected()); + BOOST_CHECK(network.getSwitch("B2").isOpen()); + + l.getTerminal().connect(); + BOOST_CHECK(network.getSwitch("B2").isOpen()); + BOOST_CHECK(l.getTerminal().isConnected()); +} + +BOOST_AUTO_TEST_CASE(NodeBreakerDisconnectDisconnectedLoad) { + Network network = createNbkNetwork(); + network.getSwitch("B3").setOpen(true); + Load& l = network.getLoad("LD"); + BOOST_CHECK(!l.getTerminal().isConnected()); + + l.getTerminal().disconnect(); + BOOST_CHECK(!network.getSwitch("B1").isOpen()); + BOOST_CHECK(!l.getTerminal().isConnected()); +} + +BOOST_AUTO_TEST_CASE(NodeBreakerDisconnectionDiamond) { + Network network = createDiamondNetwork(); + Load& l = network.getLoad("L"); + BOOST_CHECK(l.getTerminal().isConnected()); + l.getTerminal().disconnect(); + BOOST_CHECK(!l.getTerminal().isConnected()); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace iidm + +} // namespace powsybl