From df42c80027bee39399f1580bb814b1ea090aeff0 Mon Sep 17 00:00:00 2001 From: Giovanni Ferrari Date: Tue, 27 Nov 2018 10:02:50 +0100 Subject: [PATCH] Conectable switches collapser PostProcessor implementation --- connectable-switches-collapser/pom.xml | 55 +++ ...ectableSwitchesCollapserPostProcessor.java | 321 ++++++++++++++++++ .../ConnectableSwitchesCollapserTest.java | 219 ++++++++++++ pom.xml | 1 + 4 files changed, 596 insertions(+) create mode 100644 connectable-switches-collapser/pom.xml create mode 100644 connectable-switches-collapser/src/main/java/com/powsybl/iidm/postprocessor/ConnectableSwitchesCollapserPostProcessor.java create mode 100644 connectable-switches-collapser/src/test/java/eu/itesla_project/iidm/postprocessor/ConnectableSwitchesCollapserTest.java diff --git a/connectable-switches-collapser/pom.xml b/connectable-switches-collapser/pom.xml new file mode 100644 index 00000000..9ae2f34d --- /dev/null +++ b/connectable-switches-collapser/pom.xml @@ -0,0 +1,55 @@ + + + + 4.0.0 + + + eu.itesla_project + itesla-parent + 0.4.0-SNAPSHOT + + + connectable-switches-collapser + + Connectable switches collapser post processor + + + + + com.google.auto.service + auto-service + + + com.powsybl + powsybl-iidm-api + + + com.powsybl + powsybl-iidm-converter-api + + + org.slf4j + slf4j-api + + + + com.powsybl + powsybl-iidm-impl + test + + + junit + junit + test + + + diff --git a/connectable-switches-collapser/src/main/java/com/powsybl/iidm/postprocessor/ConnectableSwitchesCollapserPostProcessor.java b/connectable-switches-collapser/src/main/java/com/powsybl/iidm/postprocessor/ConnectableSwitchesCollapserPostProcessor.java new file mode 100644 index 00000000..23ad67c9 --- /dev/null +++ b/connectable-switches-collapser/src/main/java/com/powsybl/iidm/postprocessor/ConnectableSwitchesCollapserPostProcessor.java @@ -0,0 +1,321 @@ +/** + * Copyright (c) 2018, 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/. + */ +package com.powsybl.iidm.postprocessor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.auto.service.AutoService; +import com.google.common.collect.Maps; +import com.powsybl.computation.ComputationManager; +import com.powsybl.iidm.import_.ImportPostProcessor; +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.BusbarSection; +import com.powsybl.iidm.network.DanglingLine; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.HvdcConverterStation; +import com.powsybl.iidm.network.Line; +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.ShuntCompensator; +import com.powsybl.iidm.network.StaticVarCompensator; +import com.powsybl.iidm.network.Switch; +import com.powsybl.iidm.network.SwitchKind; +import com.powsybl.iidm.network.Terminal; +import com.powsybl.iidm.network.ThreeWindingsTransformer; +import com.powsybl.iidm.network.TopologyVisitor; +import com.powsybl.iidm.network.TwoWindingsTransformer; +import com.powsybl.iidm.network.VoltageLevel; + +/** + * @author Giovanni Ferrari + */ +@AutoService(ImportPostProcessor.class) +public class ConnectableSwitchesCollapserPostProcessor implements ImportPostProcessor { + + private static final Logger LOGGER = LoggerFactory + .getLogger(ConnectableSwitchesCollapserPostProcessor.class); + + @Override + public String getName() { + return "connectableSwitchesCollapser"; + } + + @Override + public void process(Network network, ComputationManager cm) throws Exception { + Objects.requireNonNull(network); + LOGGER.info("Execute {} post processor on network {}", getName(), network.getId()); + removeBreakers(network); + } + + private int removeBreakers(Network network) { + AtomicInteger removedSwitches = new AtomicInteger(0); + + network.getVoltageLevelStream().forEach(vl -> { + + int removed = removeBreakers(network, vl); + removedSwitches.addAndGet(removed); + + while (removed > 0) { + removed = removeBreakers(network, vl); + removedSwitches.addAndGet(removed); + } + removed = removeDoubleSwitchBuses(network, vl); + removedSwitches.addAndGet(removed); + + while (removed > 0) { + removed = removeDoubleSwitchBuses(network, vl); + removedSwitches.addAndGet(removed); + } + }); + LOGGER.info("Removed switches: " + removedSwitches.get()); + return removedSwitches.get(); + } + + private int removeBreakers(Network network, VoltageLevel vl) { + AtomicInteger removed = new AtomicInteger(); + + Map> busMap = getBusSwitchMap(vl); + + List singleSwitchBuses = busMap.entrySet().stream() + .filter(e -> e.getValue().size() == 1).map(r -> r.getKey()).collect(Collectors.toList()); + + List switchList = busMap.entrySet().stream() + .filter(e -> e.getValue().size() == 1).map(e -> vl.getBusBreakerView().getSwitch(e.getValue().get(0))).distinct().collect(Collectors.toList()); + + switchList.forEach(sw -> { + Bus b1 = vl.getBusBreakerView().getBus1(sw.getId()); + Bus b2 = vl.getBusBreakerView().getBus2(sw.getId()); + Bus toRemove = null; + Bus toKeep = null; + if (singleSwitchBuses.contains(b1.getId()) + && singleSwitchBuses.contains(b2.getId())) { + if (compareBusToRemove(b1, b2)) { + toRemove = b1; + toKeep = b2; + } else { + toRemove = b2; + toKeep = b1; + } + } else if (singleSwitchBuses.contains(b1.getId())) { + toRemove = b1; + toKeep = b2; + } else if (singleSwitchBuses.contains(b2.getId())) { + toRemove = b2; + toKeep = b1; + } + if (toRemove != null) { + if (this.removeSwitchandBus(vl, sw, toRemove, toKeep)) { + removed.incrementAndGet(); + } + if (toKeep.getConnectedTerminalCount() == 0) { + try { + vl.getBusBreakerView().removeBus(toKeep.getId()); + } catch (Exception ex) { + LOGGER.debug("Cannot remove bus " + toKeep.getId()); + } + } + } + }); + return removed.get(); + } + + private int removeDoubleSwitchBuses(Network network, VoltageLevel vl) { + AtomicInteger removed = new AtomicInteger(); + Map> busMap = getBusSwitchMap(vl); + + Map> doubleSwitchBuses = busMap.entrySet().stream() + .filter(e -> e.getValue().size() == 2) + .collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue())); + + doubleSwitchBuses.forEach((b, swl) -> { + Bus bus = vl.getBusBreakerView().getBus(b); + Switch s1 = vl.getBusBreakerView().getSwitch(swl.get(0)); + Switch s2 = vl.getBusBreakerView().getSwitch(swl.get(1)); + + List terminals = getTerminals(bus); + + if (s1 != null && s2 != null && terminals.size() == 0) { + Switch switchToKeep; + Switch switchToRemove; + + if (compareSwitchToRemove(vl, bus, s1, s2)) { + switchToRemove = s1; + switchToKeep = s2; + } else { + switchToRemove = s2; + switchToKeep = s1; + } + + Bus busToKeep = vl.getBusBreakerView().getBus1(switchToRemove.getId()).equals(bus) + ? vl.getBusBreakerView().getBus2(switchToRemove.getId()) + : vl.getBusBreakerView().getBus1(switchToRemove.getId()); + + boolean reconnectBus1 = vl.getBusBreakerView().getBus1(switchToKeep.getId()) + .equals(bus) ? true : false; + + vl.getBusBreakerView().removeSwitch(switchToRemove.getId()); + + String bus1 = reconnectBus1 ? busToKeep.getId() + : vl.getBusBreakerView().getBus1(switchToKeep.getId()).getId(); + String bus2 = reconnectBus1 + ? vl.getBusBreakerView().getBus2(switchToKeep.getId()).getId() + : busToKeep.getId(); + + vl.getBusBreakerView().removeSwitch(switchToKeep.getId()); + vl.getBusBreakerView().removeBus(bus.getId()); + vl.getBusBreakerView().newSwitch().setId(switchToKeep.getId()) + .setName(switchToKeep.getName()).setFictitious(switchToKeep.isFictitious()) + .setOpen(switchToKeep.isOpen() || switchToRemove.isOpen()).setBus1(bus1) + .setBus2(bus2).add(); + removed.incrementAndGet(); + } + }); + return removed.get(); + } + + private Map> getBusSwitchMap(VoltageLevel vl) { + + return vl.getBusBreakerView().getSwitchStream().flatMap(s -> Stream.of( + Maps.immutableEntry(vl.getBusBreakerView().getBus1(s.getId()).getId(), s.getId()), + Maps.immutableEntry(vl.getBusBreakerView().getBus2(s.getId()).getId(), s.getId()))) + .collect(Collectors.toMap(a -> a.getKey(), + a -> new ArrayList<>(Collections.singletonList(a.getValue())), (o, n) -> { + o.addAll(n); + return o; + }, HashMap::new)); + } + + private boolean compareSwitchToRemove(VoltageLevel vl, Bus bus, Switch s1, Switch s2) { + + if (s1.isFictitious()) { + return true; + } + if (s2.isFictitious()) { + return false; + } + if (s1.getKind() != s2.getKind()) { + return s1.getKind() == SwitchKind.BREAKER; + } + Bus b1 = vl.getBusBreakerView().getBus1(s1.getId()).equals(bus) + ? vl.getBusBreakerView().getBus2(s1.getId()) + : vl.getBusBreakerView().getBus1(s1.getId()); + Bus b2 = vl.getBusBreakerView().getBus1(s2.getId()).equals(bus) + ? vl.getBusBreakerView().getBus2(s2.getId()) + : vl.getBusBreakerView().getBus1(s2.getId()); + + if (b1.getAngle() != b2.getAngle()) { + return b1.getAngle() < b2.getAngle(); + } + return s1.getId().compareTo(s2.getId()) < 0; + } + + private boolean compareBusToRemove(Bus b1, Bus b2) { + if (b1.getConnectedTerminalCount() == b2.getConnectedTerminalCount()) { + long score1 = b1.getTwoWindingTransformerStream().count() + + b1.getThreeWindingTransformerStream().count() + b1.getLineStream().count(); + long score2 = b2.getTwoWindingTransformerStream().count() + + b2.getThreeWindingTransformerStream().count() + b2.getLineStream().count(); + return score1 <= score2; + } else { + return b1.getConnectedTerminalCount() < b2.getConnectedTerminalCount(); + } + } + + private List getTerminals(Bus b) { + List terminals = new ArrayList(); + + b.visitConnectedOrConnectableEquipments(new TopologyVisitor() { + @Override + public void visitBusbarSection(BusbarSection section) { + terminals.add(section.getTerminal()); + } + + @Override + public void visitLine(Line line, Line.Side side) { + terminals.add(line.getTerminal(side)); + } + + @Override + public void visitTwoWindingsTransformer(TwoWindingsTransformer transformer, + TwoWindingsTransformer.Side side) { + terminals.add(transformer.getTerminal(side)); + } + + @Override + public void visitThreeWindingsTransformer(ThreeWindingsTransformer transformer, + ThreeWindingsTransformer.Side side) { + terminals.add(transformer.getTerminal(side)); + } + + @Override + public void visitGenerator(Generator generator) { + terminals.add(generator.getTerminal()); + } + + @Override + public void visitLoad(Load load) { + terminals.add(load.getTerminal()); + } + + @Override + public void visitShuntCompensator(ShuntCompensator sc) { + terminals.add(sc.getTerminal()); + } + + @Override + public void visitDanglingLine(DanglingLine danglingLine) { + terminals.add(danglingLine.getTerminal()); + } + + @Override + public void visitStaticVarCompensator(StaticVarCompensator staticVarCompensator) { + terminals.add(staticVarCompensator.getTerminal()); + } + + @Override + public void visitHvdcConverterStation(HvdcConverterStation converterStation) { + terminals.add(converterStation.getTerminal()); + } + }); + return terminals; + } + + private boolean removeSwitchandBus(VoltageLevel vl, Switch s, Bus toRemove, Bus toKeep) { + List terminals = getTerminals(toRemove); + if (terminals.size() <= 1) { + terminals.forEach(t -> { + boolean reconnect = false; + if (t.isConnected()) { + t.disconnect(); + reconnect = true; + } + t.getBusBreakerView().setConnectableBus(toKeep.getId()); + + if (!s.isOpen() && reconnect) { + t.connect(); + } + }); + vl.getBusBreakerView().removeSwitch(s.getId()); + vl.getBusBreakerView().removeBus(toRemove.getId()); + return true; + } + return false; + } + + +} diff --git a/connectable-switches-collapser/src/test/java/eu/itesla_project/iidm/postprocessor/ConnectableSwitchesCollapserTest.java b/connectable-switches-collapser/src/test/java/eu/itesla_project/iidm/postprocessor/ConnectableSwitchesCollapserTest.java new file mode 100644 index 00000000..0e87e684 --- /dev/null +++ b/connectable-switches-collapser/src/test/java/eu/itesla_project/iidm/postprocessor/ConnectableSwitchesCollapserTest.java @@ -0,0 +1,219 @@ +/** + * Copyright (c) 2018, 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/. + */ +package eu.itesla_project.iidm.postprocessor; + +import java.util.Optional; + +import org.junit.Assert; +import org.junit.Test; + +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Country; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.NetworkFactory; +import com.powsybl.iidm.network.StaticVarCompensator; +import com.powsybl.iidm.network.Substation; +import com.powsybl.iidm.network.TopologyKind; +import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.iidm.postprocessor.ConnectableSwitchesCollapserPostProcessor; + +/** + * @author Giovanni Ferrari + */ +public class ConnectableSwitchesCollapserTest { + + private static final String BUS_1_ID = "bus1"; + private static final String BUS_2_ID = "bus2"; + private static final String BUS_3_ID = "bus3"; + private static final String BUS_4_ID = "bus4"; + private static final String BUS_5_ID = "bus5"; + private static final String GEN_ID = "g1"; + private static final String GEN_2_ID = "g2"; + private static final String AUX_ID = "ld1"; + private static final String AUX_2_ID = "ld2"; + private static final String AUX_3_ID = "ld3"; + private static final String AUX_5_ID = "ld5"; + private static final String SWITCH_ID = "sw1"; + private static final String SWITCH_2_ID = "sw2"; + private static final String SWITCH_3_ID = "sw3"; + private static final String VL1 = "vl1"; + private static final String VL2 = "vl2"; + + private Network createNetwork() { + Network n = NetworkFactory.create("UNIT_TEST", "MANUAL"); + Substation s = n.newSubstation() + .setId("s1") + .setCountry(Country.FR) + .add(); + VoltageLevel vl = s.newVoltageLevel() + .setId(VL1) + .setNominalV(380f) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + Bus b1 = vl.getBusBreakerView().newBus() + .setId(BUS_1_ID) + .add(); + b1.setV(412.3f); + b1.setAngle(0f); + Bus b2 = vl.getBusBreakerView().newBus() + .setId(BUS_2_ID) + .add(); + b2.setV(412.3f); + b2.setAngle(0f); + Generator g1 = vl.newGenerator() + .setId(GEN_ID) + .setBus(BUS_1_ID) + .setConnectableBus(BUS_1_ID) + .setMinP(220f) + .setMaxP(910f) + .setTargetP(910f) + .setTargetQ(79f) + .setTargetV(412.3f) + .setVoltageRegulatorOn(true) + .add(); + g1.getTerminal().setP(910f).setQ(79f); + g1.newReactiveCapabilityCurve() + .beginPoint() + .setP(220f) + .setMinQ(-520f) + .setMaxQ(300f) + .endPoint() + .beginPoint() + .setP(910f) + .setMinQ(-600f) + .setMaxQ(300f) + .endPoint() + .add(); + vl.newLoad() + .setId(AUX_ID) + .setBus(BUS_2_ID) + .setConnectableBus(BUS_2_ID) + .setP0(2) + .setQ0(3) + .add(); + + vl.newLoad() + .setId(AUX_2_ID) + .setBus(BUS_2_ID) + .setConnectableBus(BUS_2_ID) + .setP0(2) + .setQ0(3) + .add(); + + vl.getBusBreakerView().newSwitch() + .setId(SWITCH_ID) + .setBus1(BUS_1_ID) + .setBus2(BUS_2_ID) + .add(); + + Substation sub2 = n.newSubstation() + .setId("sub2") + .setCountry(Country.FR) + .add(); + VoltageLevel vl2 = sub2.newVoltageLevel() + .setId(VL2) + .setNominalV(380f) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + Bus b3 = vl2.getBusBreakerView().newBus() + .setId(BUS_3_ID) + .add(); + b3.setV(412.3f); + b3.setAngle(0f); + Bus b4 = vl2.getBusBreakerView().newBus() + .setId(BUS_4_ID) + .add(); + b4.setV(412.3f); + b4.setAngle(0f); + Bus b5 = vl2.getBusBreakerView().newBus() + .setId(BUS_5_ID) + .add(); + b5.setV(412.3f); + b5.setAngle(30f); + vl2.newLoad() + .setId(AUX_3_ID) + .setBus(BUS_3_ID) + .setConnectableBus(BUS_3_ID) + .setP0(2) + .setQ0(3) + .add(); + + vl2.newLoad() + .setId(AUX_5_ID) + .setBus(BUS_5_ID) + .setConnectableBus(BUS_5_ID) + .setP0(2) + .setQ0(3) + .add(); + + vl2.getBusBreakerView().newSwitch() + .setId(SWITCH_2_ID) + .setBus1(BUS_3_ID) + .setBus2(BUS_4_ID) + .add(); + vl2.getBusBreakerView().newSwitch() + .setId(SWITCH_3_ID) + .setBus1(BUS_4_ID) + .setBus2(BUS_5_ID) + .add(); + Generator g2 = vl2.newGenerator() + .setId(GEN_2_ID) + .setBus(BUS_3_ID) + .setConnectableBus(BUS_3_ID) + .setMinP(220f) + .setMaxP(910f) + .setTargetP(910f) + .setTargetQ(79f) + .setTargetV(412.3f) + .setVoltageRegulatorOn(true) + .add(); + g2.getTerminal().setP(910f).setQ(79f); + g2.newReactiveCapabilityCurve() + .beginPoint() + .setP(220f) + .setMinQ(-520f) + .setMaxQ(300f) + .endPoint() + .beginPoint() + .setP(910f) + .setMinQ(-600f) + .setMaxQ(300f) + .endPoint() + .add(); + vl2.newStaticVarCompensator() + .setId("SVC2") + .setConnectableBus(BUS_5_ID) + .setBus(BUS_5_ID) + .setBmin(0.0002) + .setBmax(0.0008) + .setRegulationMode(StaticVarCompensator.RegulationMode.VOLTAGE) + .setVoltageSetPoint(390) + .add(); + return n; + } + + @Test + public void test() { + ConnectableSwitchesCollapserPostProcessor remover = new ConnectableSwitchesCollapserPostProcessor(); + Network network = createNetwork(); + try { + remover.process(network, null); + } catch (Exception e) { + Assert.fail(e.getMessage()); + } + Assert.assertNull(network.getSwitch(SWITCH_ID)); + Assert.assertNull(network.getVoltageLevel(VL1).getBusBreakerView().getBus(BUS_1_ID)); + Optional gen = network.getVoltageLevel(VL1).getGeneratorStream().filter(g -> g.getId().equals(GEN_ID)).findFirst(); + Assert.assertTrue(gen.get().getTerminals().get(0).getBusBreakerView().getBus().getId().equals(BUS_2_ID)); + + Assert.assertNull(network.getSwitch(SWITCH_2_ID)); + Assert.assertNull(network.getVoltageLevel(VL2).getBusBreakerView().getBus(BUS_4_ID)); + } + + +} diff --git a/pom.xml b/pom.xml index 71f63b00..b1cbdc96 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ case-projector case-repository + connectable-switches-collapser distribution dymola-integration entsoe-case-repository