From 9c17fe0283f131fd41d0e2e178a45151e4c9e6db Mon Sep 17 00:00:00 2001 From: HARPER Jon Date: Wed, 4 Dec 2024 16:57:35 +0100 Subject: [PATCH] Improve exhaustive test, make nodebreaker consistent with invalid views, simplify code, throw exceptions instead of silently doing nothing Signed-off-by: HARPER Jon --- .../store/iidm/impl/AbstractTopology.java | 93 ++- .../store/iidm/impl/CalculatedBus.java | 130 ++-- .../store/iidm/impl/ConfiguredBusImpl.java | 97 ++- .../network/store/iidm/impl/LineTest.java | 20 +- ...geLevelSetVAngleInCalculatedViewsTest.java | 667 ++++++++++++++++++ .../store/iidm/impl/VoltageLevelSetVTest.java | 336 --------- .../store/iidm/impl/VoltageLevelTest.java | 7 +- 7 files changed, 834 insertions(+), 516 deletions(-) create mode 100644 network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelSetVAngleInCalculatedViewsTest.java delete mode 100644 network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelSetVTest.java diff --git a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/AbstractTopology.java b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/AbstractTopology.java index c7e089d1d..e6df13b73 100644 --- a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/AbstractTopology.java +++ b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/AbstractTopology.java @@ -6,7 +6,6 @@ */ package com.powsybl.network.store.iidm.impl; -import com.google.common.util.concurrent.AtomicDouble; import com.powsybl.commons.config.PlatformConfig; import com.powsybl.iidm.network.*; import com.powsybl.network.store.model.*; @@ -17,7 +16,6 @@ import org.jgrapht.graph.DirectedPseudograph; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; /** @@ -362,58 +360,50 @@ private void setCalculatedBuses(Resource voltageLevelRes } } - private void getVAndAngleFromConfiguredBus(NetworkObjectIndex index, - Resource voltageLevelResource, - ConnectedSetResult connectedSet, - AtomicDouble v, - AtomicDouble angle) { - AtomicReference foundConfiguredBus = new AtomicReference<>(); - index.getConfiguredBuses(voltageLevelResource.getId()).forEach(bus -> { - if (foundConfiguredBus.get() == null) { - ConfiguredBusImpl configuredBus = (ConfiguredBusImpl) bus; - configuredBus.getAllTerminals().stream() - .filter(Terminal::isConnected) - .forEach(t -> { - if (foundConfiguredBus.get() == null) { - connectedSet.getConnectedVertices().stream().filter(vertex -> - vertex.getId().equals(t.getConnectable().getId()) - ).findFirst().ifPresent(vertex -> foundConfiguredBus.set(configuredBus)); - } - }); - } - }); - if (foundConfiguredBus.get() != null) { - v.set(foundConfiguredBus.get().getResource().getAttributes().getV()); - angle.set(foundConfiguredBus.get().getResource().getAttributes().getAngle()); - } - } - - private void getVAndAngleFromOtherView(NetworkObjectIndex index, - Resource voltageLevelResource, - ConnectedSetResult connectedSet, - AtomicDouble v, - AtomicDouble angle, - boolean isBusView) { - if (voltageLevelResource.getAttributes().getTopologyKind() == TopologyKind.NODE_BREAKER) { + private CalculatedBusAttributes findFirstMatchingNodeBreakerCalculatedBusAttributes(Resource voltageLevelResource, + ConnectedSetResult connectedSet, boolean isBusView) { + // TODO Some day we may decide to start preserving values in nodebreaker topology after invalidating the views, + // so we could remove this check for isCalculatedBusesValid. Here it controls preserving + // the value accross the other view, we have it to be consistent with preserving values + // from the same view (currently not preserved) + if (voltageLevelResource.getAttributes().isCalculatedBusesValid()) { List calculatedBusAttributes = isBusView ? voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView() : voltageLevelResource.getAttributes().getCalculatedBusesForBusView(); if (!CollectionUtils.isEmpty(calculatedBusAttributes)) { Map nodesToCalculatedBuses = isBusView ? voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusBreakerView() : voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusView(); if (!MapUtils.isEmpty(nodesToCalculatedBuses)) { - Integer node = (Integer) connectedSet.getConnectedNodesOrBuses().iterator().next(); + Integer node = (Integer) connectedSet.getConnectedNodesOrBuses().iterator().next(); // non deterministic Integer busNum = nodesToCalculatedBuses.get(node); if (busNum != null) { - CalculatedBusAttributes busAttributes = calculatedBusAttributes.get(busNum); - if (busAttributes != null) { - v.set(busAttributes.getV()); - angle.set(busAttributes.getAngle()); - } + return calculatedBusAttributes.get(busNum); } } } - } else { - // get V and Angle values from configured buses - getVAndAngleFromConfiguredBus(index, voltageLevelResource, connectedSet, v, angle); } + return null; + } + + private CalculatedBusAttributes createCalculatedBusAttributesWithVAndAngle(NetworkObjectIndex index, + Resource voltageLevelResource, + ConnectedSetResult connectedSet, + boolean isBusView) { + double v = Double.NaN; + double angle = Double.NaN; + if (voltageLevelResource.getAttributes().getTopologyKind() == TopologyKind.NODE_BREAKER) { + CalculatedBusAttributes busAttributes = findFirstMatchingNodeBreakerCalculatedBusAttributes(voltageLevelResource, connectedSet, isBusView); + if (busAttributes != null) { + v = busAttributes.getV(); + angle = busAttributes.getAngle(); + } + } else { // BUS_BREAKER + // currently for busbreakertopology the values are always preserved + // when using only the busbreakerview. So mimic the behavior and always preserve them + // when using the busview: get V and Angle values from configured buses + String configuredBusId = ((ConnectedSetResult) connectedSet).getConnectedNodesOrBuses().iterator().next(); // nondeterministic + Bus b = index.getConfiguredBus(configuredBusId).orElseThrow(); + v = b.getV(); + angle = b.getAngle(); + } + return new CalculatedBusAttributes(connectedSet.getConnectedVertices(), null, null, v, angle); } private CalculationResult getCalculatedBusAttributesList(NetworkObjectIndex index, Resource voltageLevelResource, boolean isBusView) { @@ -423,16 +413,17 @@ private CalculationResult getCalculatedBusAttributesList(NetworkObjectIndex i calculatedBusAttributesList = isBusView ? voltageLevelResource.getAttributes().getCalculatedBusesForBusView() : voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView(); nodeOrBusToCalculatedBusNum = getNodeOrBusToCalculatedBusNum(voltageLevelResource, isBusView); } else { + // calculate buses List> connectedSetList = findConnectedSetList(index, voltageLevelResource, isBusView); calculatedBusAttributesList = connectedSetList .stream() - .map(connectedSet -> { - AtomicDouble v = new AtomicDouble(Double.NaN); - AtomicDouble angle = new AtomicDouble(Double.NaN); - // get V and Angle values from other view if available - getVAndAngleFromOtherView(index, voltageLevelResource, connectedSet, v, angle, isBusView); - return new CalculatedBusAttributes(connectedSet.getConnectedVertices(), null, null, v.get(), angle.get()); - }) + //TODO in this case in nodebreaker topology we currently don't preserve any values from + //the same view if it was already computed but is invalidated. + //we could do it some day (we need to define good heuristics to + //match previous values to new buses). + //NOTE: We chose to have the same behavior when getting the values from the other view + // get V and Angle values from other view if available + .map(connectedSet -> createCalculatedBusAttributesWithVAndAngle(index, voltageLevelResource, connectedSet, isBusView)) .collect(Collectors.toList()); setCalculatedBuses(voltageLevelResource, isBusView, calculatedBusAttributesList); diff --git a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CalculatedBus.java b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CalculatedBus.java index 5f96222ba..32fc88dc0 100644 --- a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CalculatedBus.java +++ b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/CalculatedBus.java @@ -19,10 +19,10 @@ import org.apache.commons.collections4.MapUtils; import java.util.*; +import java.util.Map.Entry; import java.util.function.Function; import java.util.function.ObjDoubleConsumer; import java.util.function.Predicate; -import java.util.function.ToDoubleFunction; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -52,9 +52,6 @@ public final class CalculatedBus implements BaseBus { private final Function getBusFromTerminal; - private static final String VOLTAGE = "v"; - private static final String ANGLE = "angle"; - CalculatedBus(NetworkObjectIndex index, String voltageLevelId, String id, String name, Resource voltageLevelResource, int calculatedBusNum, boolean isBusView) { this.index = Objects.requireNonNull(index); @@ -142,24 +139,16 @@ private void setVInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes calculatedBusAttributes.setV(value); } - private void setVInConfiguredBus(ConfiguredBusAttributes configuredBusAttributes, double value) { - configuredBusAttributes.setV(value); - } - - private double getVInBus(Bus bus) { - return bus.getV(); + private void setVInConfiguredBus(ConfiguredBusImpl configuredBus, double value) { + configuredBus.setConfiguredBusV(value); } private void setAngleInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) { calculatedBusAttributes.setAngle(value); } - private void setAngleInConfiguredBus(ConfiguredBusAttributes configuredBusAttributes, double value) { - configuredBusAttributes.setAngle(value); - } - - private double getAngleInBus(Bus bus) { - return bus.getAngle(); + private void setAngleInConfiguredBus(ConfiguredBusImpl configuredBus, double value) { + configuredBus.setConfiguredBusAngle(value); } @Override @@ -169,7 +158,8 @@ public Bus setV(double v) { if (getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) { // update V in configured buses - updateConfiguredBuses(v, VOLTAGE, this::getVInBus, this::setVInConfiguredBus); + // this triggers network notifications for 'v' for each configured bus + updateConfiguredBuses(v, this::setVInConfiguredBus); } else { // update V for buses in the other view (busView/busBreakerView) updateBusesAttributes(v, this::setVInCalculatedBus); @@ -177,62 +167,6 @@ public Bus setV(double v) { return this; } - private void updateBusesAttributes(double value, ObjDoubleConsumer setValue) { - VoltageLevelAttributes vlAttributes = ((VoltageLevelImpl) getVoltageLevel()).getResource().getAttributes(); - Map nodesToCalculatedBuses = isBusView - ? vlAttributes.getNodeToCalculatedBusForBusView() - : vlAttributes.getNodeToCalculatedBusForBusBreakerView(); - if (!MapUtils.isEmpty(nodesToCalculatedBuses)) { - nodesToCalculatedBuses.entrySet().stream() - .filter(entry -> getCalculatedBusNum() == entry.getValue()) - .map(Map.Entry::getKey) - .forEach(node -> { - Map nodesToCalculatedBusesInOtherView = isBusView - ? vlAttributes.getNodeToCalculatedBusForBusBreakerView() - : vlAttributes.getNodeToCalculatedBusForBusView(); - if (!MapUtils.isEmpty(nodesToCalculatedBusesInOtherView) && - nodesToCalculatedBusesInOtherView.containsKey(node)) { - int busNumInOtherView = nodesToCalculatedBusesInOtherView.get(node); - List calculatedBusAttributes = isBusView - ? vlAttributes.getCalculatedBusesForBusBreakerView() - : vlAttributes.getCalculatedBusesForBusView(); - if (!CollectionUtils.isEmpty(calculatedBusAttributes)) { - setValue.accept(calculatedBusAttributes.get(busNumInOtherView), value); - index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV); - } - } - }); - } - } - - private void updateConfiguredBuses(double newValue, - String attributeName, - ToDoubleFunction getValue, - ObjDoubleConsumer setValue) { - List buses = ((VoltageLevelImpl) getVoltageLevel()).getResource().getAttributes() - .getBusToCalculatedBusForBusView().entrySet().stream() - .filter(entry -> getCalculatedBusNum() == entry.getValue()) - .map(Map.Entry::getKey) - .map(getVoltageLevel().getBusBreakerView()::getBus) - .toList(); - - Map> oldNewValues = buses.stream() - .collect(Collectors.toMap( - bus -> bus, - bus -> new AbstractMap.SimpleEntry<>(getValue.applyAsDouble(bus), newValue) - )); - - buses.forEach(bus -> { - setValue.accept(((ConfiguredBusImpl) bus).getResource().getAttributes(), newValue); - index.updateConfiguredBusResource(((ConfiguredBusImpl) bus).getResource(), null); - }); - - String variantId = index.getNetwork().getVariantManager().getWorkingVariantId(); - oldNewValues.forEach((bus, oldNewValue) -> - index.notifyUpdate(bus, attributeName, variantId, oldNewValue.getKey(), oldNewValue.getValue()) - ); - } - @Override public double getAngle() { return getAttributes().getAngle(); @@ -245,7 +179,8 @@ public Bus setAngle(double angle) { if (getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) { // update angle in configuredBus - updateConfiguredBuses(angle, ANGLE, this::getAngleInBus, this::setAngleInConfiguredBus); + // this triggers network notifications for 'angle' for each configured bus + updateConfiguredBuses(angle, this::setAngleInConfiguredBus); } else { // update angle for buses in the other view (busView/busBreakerView) updateBusesAttributes(angle, this::setAngleInCalculatedBus); @@ -526,10 +461,6 @@ public Stream getAllTerminalsStream() { .filter(t -> t.getVoltageLevel().getId().equals(getVoltageLevel().getId()) && pred.test(t)); } - public int getCalculatedBusNum() { - return calculatedBusNum; - } - @Override public > void addExtension(Class aClass, E e) { throw new UnsupportedOperationException("Adding an extension on calculated bus is not authorized"); @@ -574,4 +505,47 @@ public , B extends ExtensionAdder> B newExtensi ExtensionAdderProvider provider = ExtensionAdderProviders.findCachedProvider(getImplementationName(), type); return (B) provider.newAdder(this); } + + public int getCalculatedBusNum() { + return calculatedBusNum; + } + + private void updateBusesAttributes(double value, ObjDoubleConsumer setValue) { + // busnum of this bus -> nodes in this bus -> busnums in the other view -> buses of the other view + VoltageLevelAttributes vlAttributes = ((VoltageLevelImpl) getVoltageLevel()).getResource().getAttributes(); + Map nodesToCalculatedBuses = isBusView + ? vlAttributes.getNodeToCalculatedBusForBusView() + : vlAttributes.getNodeToCalculatedBusForBusBreakerView(); + Map nodesToCalculatedBusesInOtherView = isBusView + ? vlAttributes.getNodeToCalculatedBusForBusBreakerView() + : vlAttributes.getNodeToCalculatedBusForBusView(); + if (!MapUtils.isEmpty(nodesToCalculatedBuses) && !MapUtils.isEmpty(nodesToCalculatedBusesInOtherView)) { + for (Entry entry : nodesToCalculatedBuses.entrySet()) { + if (getCalculatedBusNum() == entry.getValue()) { + int node = entry.getKey(); + if (nodesToCalculatedBusesInOtherView.containsKey(node)) { + int busNumInOtherView = nodesToCalculatedBusesInOtherView.get(node); + List calculatedBusAttributes = isBusView + ? vlAttributes.getCalculatedBusesForBusBreakerView() + : vlAttributes.getCalculatedBusesForBusView(); + if (!CollectionUtils.isEmpty(calculatedBusAttributes)) { + setValue.accept(calculatedBusAttributes.get(busNumInOtherView), value); + index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV); + } + } + } + } + } + } + + private void updateConfiguredBuses(double newValue, + ObjDoubleConsumer setValue) { + VoltageLevelAttributes vlAttributes = ((VoltageLevelImpl) getVoltageLevel()).getResource().getAttributes(); + for (Entry entry : vlAttributes.getBusToCalculatedBusForBusView().entrySet()) { + if (getCalculatedBusNum() == entry.getValue()) { + Bus bus = getVoltageLevel().getBusBreakerView().getBus(entry.getKey()); + setValue.accept((ConfiguredBusImpl) bus, newValue); + } + } + } } diff --git a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/ConfiguredBusImpl.java b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/ConfiguredBusImpl.java index 2c5b1a971..4f75ebe21 100644 --- a/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/ConfiguredBusImpl.java +++ b/network-store-iidm-impl/src/main/java/com/powsybl/network/store/iidm/impl/ConfiguredBusImpl.java @@ -82,28 +82,24 @@ public double getAngle() { return getResource().getAttributes().getAngle(); } - private void setVInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) { - calculatedBusAttributes.setV(value); - } + private void setV(double v, boolean updateCalculatedBus) { + double oldValue = getResource().getAttributes().getV(); + if (v != oldValue) { + updateResource(res -> res.getAttributes().setV(v)); + String variantId = index.getNetwork().getVariantManager().getWorkingVariantId(); + index.notifyUpdate(this, "v", variantId, oldValue, v); - private void setAngleInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) { - calculatedBusAttributes.setAngle(value); + if (updateCalculatedBus) { + // update V for bus in BusView + updateCalculatedBusAttributes(v, getResource().getAttributes().getVoltageLevelId(), this::setVInCalculatedBus); + } + } } - private void updateCalculatedBusAttributes(double newValue, - String voltageLevelId, - ObjDoubleConsumer setValue) { - index.getVoltageLevel(voltageLevelId).ifPresent(voltageLevel -> { - Map calculatedBuses = voltageLevel.getResource().getAttributes().getBusToCalculatedBusForBusView(); - if (!MapUtils.isEmpty(calculatedBuses)) { - Integer busviewnum = calculatedBuses.get(getId()); - if (busviewnum != null) { - CalculatedBusAttributes busviewattributes = voltageLevel.getResource().getAttributes().getCalculatedBusesForBusView().get(busviewnum); - setValue.accept(busviewattributes, newValue); - index.updateVoltageLevelResource(voltageLevel.getResource(), AttributeFilter.SV); - } - } - }); + // update without the part setting values in calculated buses otherwise + // it leads to infinite loops because calculated buses also update configured buses + void setConfiguredBusV(double v) { + setV(v, false); } @Override @@ -111,32 +107,69 @@ public Bus setV(double v) { if (v < 0) { throw new ValidationException(this, "voltage cannot be < 0"); } - double oldValue = getResource().getAttributes().getV(); - if (v != oldValue) { - updateResource(res -> res.getAttributes().setV(v)); - String variantId = index.getNetwork().getVariantManager().getWorkingVariantId(); - index.notifyUpdate(this, "v", variantId, oldValue, v); - - // update V for bus in BusView - updateCalculatedBusAttributes(v, getResource().getAttributes().getVoltageLevelId(), this::setVInCalculatedBus); - } + setV(v, true); return this; } - @Override - public Bus setAngle(double angle) { + void setAngle(double angle, boolean updateCalculatedBus) { double oldValue = getResource().getAttributes().getAngle(); if (angle != oldValue) { updateResource(res -> res.getAttributes().setAngle(angle)); String variantId = index.getNetwork().getVariantManager().getWorkingVariantId(); index.notifyUpdate(this, "angle", variantId, oldValue, angle); - // update angle for bus in BusView - updateCalculatedBusAttributes(angle, getResource().getAttributes().getVoltageLevelId(), this::setAngleInCalculatedBus); + if (updateCalculatedBus) { + // update angle for bus in BusView + updateCalculatedBusAttributes(angle, getResource().getAttributes().getVoltageLevelId(), this::setAngleInCalculatedBus); + } } + } + + // update without the part setting values in calculated buses otherwise + // it leads to infinite loops because calculated buses also update configured buses + void setConfiguredBusAngle(double angle) { + setAngle(angle, false); + } + + @Override + public Bus setAngle(double angle) { + setAngle(angle, true); return this; } + private void setVInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) { + calculatedBusAttributes.setV(value); + } + + private void setAngleInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) { + calculatedBusAttributes.setAngle(value); + } + + private void updateCalculatedBusAttributes(double newValue, + String voltageLevelId, + ObjDoubleConsumer setValue) { + VoltageLevelImpl voltageLevel = index.getVoltageLevel(voltageLevelId).orElseThrow(); + Map calculatedBuses = voltageLevel.getResource().getAttributes().getBusToCalculatedBusForBusView(); + // TODO, do we want to update the busview values if the view is invalid ? + // if invalid, values will be restored from the configuredbuses on the next + // busview computation anyway in the new bus objects and attributes, + // but the previous bus objects and attributes may still be used somewhere + // so there is a visible effect to choosing to update invalid views or not. + if (!MapUtils.isEmpty(calculatedBuses)) { + Integer busviewnum = calculatedBuses.get(getId()); + if (busviewnum != null) { + CalculatedBusAttributes busviewattributes = voltageLevel.getResource().getAttributes().getCalculatedBusesForBusView().get(busviewnum); + // Same code as the iidm impl for CalculatedBus setV / setAngle + // (without the part setting values in configured buses otherwise + // it would be an infinite loop), but copy paste here + // to avoid creating the object (calculated buses are created on when computing + // the bus view, but we want to only update if the busview exist, not force its creation) + setValue.accept(busviewattributes, newValue); + index.updateVoltageLevelResource(voltageLevel.getResource(), AttributeFilter.SV); + } + } + } + @Override public double getFictitiousP0() { return getResource().getAttributes().getFictitiousP0(); diff --git a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/LineTest.java b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/LineTest.java index 0cac6d2f4..943dea2d7 100644 --- a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/LineTest.java +++ b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/LineTest.java @@ -10,20 +10,7 @@ import com.powsybl.cgmes.conformity.CgmesConformity1Catalog; import com.powsybl.cgmes.conversion.CgmesImport; import com.powsybl.commons.PowsyblException; -import com.powsybl.iidm.network.ActivePowerLimits; -import com.powsybl.iidm.network.ApparentPowerLimits; -import com.powsybl.iidm.network.Bus; -import com.powsybl.iidm.network.CurrentLimits; -import com.powsybl.iidm.network.DanglingLine; -import com.powsybl.iidm.network.DanglingLineFilter; -import com.powsybl.iidm.network.Importer; -import com.powsybl.iidm.network.LimitType; -import com.powsybl.iidm.network.Line; -import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.Terminal; -import com.powsybl.iidm.network.TieLine; -import com.powsybl.iidm.network.TwoSides; -import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.ConnectablePosition; import com.powsybl.iidm.network.extensions.ConnectablePositionAdder; import org.junit.Test; @@ -32,10 +19,7 @@ import java.util.Properties; import java.util.stream.Collectors; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThrows; +import static org.junit.Assert.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; diff --git a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelSetVAngleInCalculatedViewsTest.java b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelSetVAngleInCalculatedViewsTest.java new file mode 100644 index 000000000..3b6809963 --- /dev/null +++ b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelSetVAngleInCalculatedViewsTest.java @@ -0,0 +1,667 @@ +/** + * Copyright (c) 2020, 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.network.store.iidm.impl; + +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.SwitchKind; +import com.powsybl.iidm.network.TopologyKind; +import com.powsybl.iidm.network.VoltageLevel; +import org.junit.Test; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Jon Harper + * + * complex specific exhaustive tests for setV and setAngle interactions with calculated views. + */ +public class VoltageLevelSetVAngleInCalculatedViewsTest { + @Test + public void testWithMultipleBusInBusBreakerAndBusView() { + testSetMultipleBusAcrossViews(() -> { + Network network = Network.create("test_mcc", "test"); + return createNodeBreaker(network); + }, NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS, NBVL_BUSVIEWBUS_TO_BUSBREAKERVIEWBUS, Bus::getV, Bus::setV); + testSetMultipleBusAcrossViews(() -> { + Network network = Network.create("test_mcc", "test"); + return createNodeBreaker(network); + }, NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS, NBVL_BUSVIEWBUS_TO_BUSBREAKERVIEWBUS, Bus::getAngle, Bus::setAngle); + testSetMultipleBusAcrossViews(() -> { + Network network = Network.create("test_mcc", "test"); + return createBusBreaker(network); + }, BBVL_CONFIGUREDBUS_TO_BUSVIEWBUS, BBVL_BUSVIEWBUS_TO_CONFIGUREDBUS, Bus::getV, Bus::setV); + testSetMultipleBusAcrossViews(() -> { + Network network = Network.create("test_mcc", "test"); + return createBusBreaker(network); + }, BBVL_CONFIGUREDBUS_TO_BUSVIEWBUS, BBVL_BUSVIEWBUS_TO_CONFIGUREDBUS, Bus::getAngle, Bus::setAngle); + } + + //NOTE: id in both cases is the voltage level id suffixed by the smallest + //node number of the nodebreaker definition connected to the bus. + private static final Map NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS = new TreeMap<>(Map.ofEntries( + Map.entry("VL1_0", ""), + Map.entry("VL1_1", "VL1_1"), + Map.entry("VL1_3", "VL1_1"), + Map.entry("VL1_4", "VL1_4"), + Map.entry("VL1_5", "VL1_5"), + Map.entry("VL1_6", "VL1_5"), + Map.entry("VL1_7", "VL1_5"), + Map.entry("VL1_8", "VL1_5"), + Map.entry("VL1_9", ""), + Map.entry("VL1_17", ""), + Map.entry("VL1_18", ""), + Map.entry("VL1_19", "VL1_1") + )); + private static final Map> NBVL_BUSVIEWBUS_TO_BUSBREAKERVIEWBUS = invertMap(NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS); + + private static VoltageLevelImpl createNodeBreaker(Network network) { + // nodebreaker topology voltage level + VoltageLevel vl1 = network.newVoltageLevel() + .setId("VL1") + .setNominalV(225.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + // This bbs is not even in the same graph as the other stuff and has no equipment connected + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS_ALONE") + .setNode(0) + .add(); + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS1") + .setNode(1) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW1") + .setKind(SwitchKind.BREAKER) + .setRetained(false) + .setOpen(false) + .setNode1(1) + .setNode2(2) + .add(); + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS2") + .setNode(2) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW2") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(false) + .setNode1(2) + .setNode2(3) + .add(); + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS3") + .setNode(3) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW3") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(true) + .setNode1(3) + .setNode2(4) + .add(); + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS4") + .setNode(4) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW4") + .setKind(SwitchKind.BREAKER) + .setRetained(false) + .setOpen(true) + .setNode1(4) + .setNode2(5) + .add(); + + // add busbar sections in star coupling to have a bus in the middle without any equipment. It will + // exist in the busbreaker view but not in the busview. For this one use closed switches so that it + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS5") + .setNode(5) + .add(); + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS6") + .setNode(6) + .add(); + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS7") + .setNode(7) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW_STAR1") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(false) + .setNode1(5) + .setNode2(8) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW_STAR2") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(false) + .setNode1(6) + .setNode2(8) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW_STAR3") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(false) + .setNode1(7) + .setNode2(8) + .add(); + // same star coupling as before but with open switches so that + // the central bus is not mapped to anything in the bus view + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW_OPENSTAR1") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(true) + .setNode1(5) + .setNode2(9) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW_OPENSTAR2") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(true) + .setNode1(6) + .setNode2(9) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW_OPENSTAR3") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(true) + .setNode1(7) + .setNode2(9) + .add(); + + // add loads so that buses are not pruned + vl1.getNodeBreakerView().newInternalConnection() + .setNode1(1) + .setNode2(10) + .add(); + vl1.newLoad().setId("VL1L1").setNode(10).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + vl1.getNodeBreakerView().newInternalConnection() + .setNode1(2) + .setNode2(11) + .add(); + vl1.newLoad().setId("VL1L2").setNode(11).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + vl1.getNodeBreakerView().newInternalConnection() + .setNode1(3) + .setNode2(12) + .add(); + vl1.newLoad().setId("VL1L3").setNode(12).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + vl1.getNodeBreakerView().newInternalConnection() + .setNode1(4) + .setNode2(13) + .add(); + vl1.newLoad().setId("VL1L4").setNode(13).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + vl1.getNodeBreakerView().newInternalConnection() + .setNode1(5) + .setNode2(14) + .add(); + vl1.newLoad().setId("VL1L5").setNode(14).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + vl1.getNodeBreakerView().newInternalConnection() + .setNode1(6) + .setNode2(15) + .add(); + vl1.newLoad().setId("VL1L6").setNode(15).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + vl1.getNodeBreakerView().newInternalConnection() + .setNode1(7) + .setNode2(16) + .add(); + vl1.newLoad().setId("VL1L7").setNode(16).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + //add a load on bbs1 but not connected, it will be in a separate floating bus in the busbreakerview and pruned in the busview + vl1.getNodeBreakerView().newSwitch() + .setId("VL1_SW_L8") + .setKind(SwitchKind.BREAKER) + .setRetained(false) + .setOpen(true) + .setNode1(1) + .setNode2(17) + .add(); + vl1.newLoad().setId("VL1L8").setNode(17).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + //add a load on bbs1 but not connected but with a retained switch, it will be in a separate bus in the busbreakerview and pruned in the busview + vl1.getNodeBreakerView().newSwitch() + .setId("VL1_SW_L9") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(true) + .setNode1(1) + .setNode2(18) + .add(); + vl1.newLoad().setId("VL1L9").setNode(18).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + //add a load on bbs1 but with retained switch, it will be in a separate bus in the busbreakerview but connected in the busview + vl1.getNodeBreakerView().newSwitch() + .setId("VL1_SW_L10") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(false) + .setNode1(1) + .setNode2(19) + .add(); + vl1.newLoad().setId("VL1L10").setNode(19).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + return (VoltageLevelImpl) vl1; + } + + //NOTE: id for the busbreakerview is the configured bus; + // id for the busview is order of traversal during busview computation + private static final Map BBVL_CONFIGUREDBUS_TO_BUSVIEWBUS = new TreeMap<>(Map.of( + "BUS_ALONE", "", + "BUS1", "VL2_0", + "BUS2", "VL2_0", + "BUS3", "", + "BUS4", "" + )); + + private static final Map> BBVL_BUSVIEWBUS_TO_CONFIGUREDBUS = invertMap(BBVL_CONFIGUREDBUS_TO_BUSVIEWBUS); + + private static VoltageLevelImpl createBusBreaker(Network network) { + // busbreaker topology voltage level + VoltageLevel vl2 = network.newVoltageLevel() + .setId("VL2") + .setNominalV(225.0) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + // This bus is not even in the same graph as the other stuff and has no equipment connected + vl2.getBusBreakerView().newBus() + .setId("BUS_ALONE") + .add(); + vl2.getBusBreakerView().newBus() + .setId("BUS1") + .add(); + vl2.getBusBreakerView().newSwitch() + .setId("VL2SW1") + .setOpen(false) + .setBus1("BUS1") + .setBus2("BUS2") + .add(); + vl2.getBusBreakerView().newBus() + .setId("BUS2") + .add(); + vl2.getBusBreakerView().newSwitch() + .setId("VL2SW2") + .setOpen(true) + .setBus1("BUS2") + .setBus2("BUS3") + .add(); + vl2.getBusBreakerView().newBus() + .setId("BUS3") + .add(); + vl2.getBusBreakerView().newSwitch() + .setId("VL2SW3") + .setOpen(true) + .setBus1("BUS3") + .setBus2("BUS4") + .add(); + vl2.getBusBreakerView().newBus() + .setId("BUS4") + .add(); + + // loads to avoid pruning + vl2.newLoad().setId("VL2L1").setBus("BUS1").setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + //no load on BUS2 but it is connected to bus1 + //no load on BUS3 so that it is prunned, disconnected from everything + vl2.newLoad().setId("VL2L4").setConnectableBus("BUS4").setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + return (VoltageLevelImpl) vl2; + } + + private void testSetMultipleBusAcrossViews(Supplier networkVoltageLevelSupplier, Map busBreakerViewBusToBusViewBus, Map> busViewBusToBusBreakerViewBus, + Function getter, BiConsumer setter) { + + VoltageLevel vl; + // tests for busbreakerview set + + // test with views that are initialized but unset, new network each time + // currently no need to be deterministic for this to work, the last set + // in the busbreakerview is applied to the busview + for (Map.Entry entry : busBreakerViewBusToBusViewBus.entrySet()) { + String busbreakerviewbusid = entry.getKey(); + String busviewbusid = entry.getValue(); + vl = networkVoltageLevelSupplier.get(); + if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) { + // this triggers view calculation only for nodebreakertopology + vl.getBusBreakerView().getBuses(); + } + vl.getBusView().getBuses(); + setAndCheckBusBreakerBus(vl, busbreakerviewbusid, busviewbusid, getter, setter); + } + + // test with views that are initialized and everything set to nan, reuse network + // currently no need to be deterministic for this to work, the last set + // in the busbreakerview is applied to the busview + vl = networkVoltageLevelSupplier.get(); + for (Map.Entry entry : busBreakerViewBusToBusViewBus.entrySet()) { + String busbreakerviewbusid = entry.getKey(); + String busviewbusid = entry.getValue(); + setAllBbvbAndBvb(vl, Double.NaN, setter); + setAndCheckBusBreakerBus(vl, busbreakerviewbusid, busviewbusid, getter, setter); + } + + // test with both views invalid, new network each time, + // in this case we need to set all bbvbs to get a deterministic behavior + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + vl = networkVoltageLevelSupplier.get(); + if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) { + // this triggers view calculation only for nodebreakertopology + vl.getBusBreakerView().getBuses(); + } + vl.getBusView().getBuses(); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + setAndCheckBusBreakerBusDeterministic(vl, busbreakerviewbusids, busviewbusid, getter, setter); + } + + // test with busview invalid, busbreakerview unset, new network each time + // in this case we need to set all bbvbs to get a deterministic behavior + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + vl = networkVoltageLevelSupplier.get(); + vl.getBusView().getBuses(); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + setAndCheckBusBreakerBusDeterministic(vl, busbreakerviewbusids, busviewbusid, getter, setter); + } + + if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) { + // this test of initiliazed busbreakeriewbus only makes sense for nodebreaker topology, + // for busbreakertopology it's the same as all views uninitialized + + // test with busbreakerview invalid, busview unset + // in this case we need to set all bbvbs to get a deterministic behavior + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + vl = networkVoltageLevelSupplier.get(); + vl.getBusBreakerView().getBuses(); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + setAndCheckBusBreakerBusDeterministic(vl, busbreakerviewbusids, busviewbusid, getter, setter); + } + } + + // test with views that are not initialized at all, new network each time + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + vl = networkVoltageLevelSupplier.get(); + setAndCheckBusBreakerBusDeterministic(vl, busbreakerviewbusids, busviewbusid, getter, setter); + } + + // test with views that are initialized and everything set to a real value, reuse network. + // With invalid views it should not propagate to the other view + // TODO currently we have this behavior between the 2 views to + // mimic the behavior when only the same view is set (values are not carried over + // when the new view is computed after invalidating it). But if we change this behavior, + // we may change the behavior when there are two views to mimic it, and so we should invert this test. + // for the busbreakertopology, it's as if the busbreakerview is always valid as it's directly + // the configured buses, so it keeps the values. It doesn't make much sense but that's how it is. + if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) { + // test one same view + vl = networkVoltageLevelSupplier.get(); + setAllBbvb(vl, 1.0, setter); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + for (Bus bbvb : vl.getBusBreakerView().getBuses()) { + assertTrue("case nodebreakertopology " + bbvb.getId() + " (busbreakerviewbus) is set, should not have been carried over from invalid previous view", + Double.isNaN(getter.apply(bbvb))); + } + + // test one other view + vl = networkVoltageLevelSupplier.get(); + setAllBvb(vl, 1.0, setter); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + for (Bus bbvb : vl.getBusBreakerView().getBuses()) { + assertTrue("case nodebreakertopology " + bbvb.getId() + " (busbreakerviewbus) is set, should not have been carried over from invalid previous view", + Double.isNaN(getter.apply(bbvb))); + } + } else { // BUS_BREAKER + // No need to test the busbreakerview in busbreakertopology, + // it's not a view it's directly the configured buses. + // so test only with busview view + // NOTE: for busbreakertopology we keep the previous values. + vl = networkVoltageLevelSupplier.get(); + setAllBvb(vl, 1.0, setter); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + for (Bus bbvb : vl.getBusBreakerView().getBuses()) { + if (!busBreakerViewBusToBusViewBus.get(bbvb.getId()).isEmpty()) { + //only test non pruned bbvb because we set in the busview, so pruned bbvb are unset + assertEquals("case busbreakertopology not pruned " + bbvb.getId() + " (busbreakerviewbus) is unset, but should have been set when setting on the busview", + 1, getter.apply(bbvb), 0.0); + } else { + // the pruned ones should be unset + assertTrue("case busbreakertopology pruned " + bbvb.getId() + " (busbreakerviewbus) is set, should not have been set because it is pruned from the busview", + Double.isNaN(getter.apply(bbvb))); + } + } + } + + // tests for busbreakerview set + + // test with views that are initialized but unset, new network each time + // currently no need to be deterministic for this to work, the last set + // in the busbreakerview is applied to the busview + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + vl = networkVoltageLevelSupplier.get(); + if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) { + // this triggers view calculation only for nodebreakertopology + vl.getBusBreakerView().getBuses(); + } + setAndCheckBusViewBus(vl, busviewbusid, busbreakerviewbusids, getter, setter); + } + + // test with views that is initialized and everything set to nan, reuse network + vl = networkVoltageLevelSupplier.get(); + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + setAllBbvbAndBvb(vl, Double.NaN, setter); + setAndCheckBusViewBus(vl, busviewbusid, busbreakerviewbusids, getter, setter); + } + + // test with both views invalid, new network each time + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + vl = networkVoltageLevelSupplier.get(); + if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) { + // this triggers view calculation only for nodebreakertopology + vl.getBusBreakerView().getBuses(); + } + vl.getBusView().getBuses(); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + setAndCheckBusViewBus(vl, busviewbusid, busbreakerviewbusids, getter, setter); + } + + // test with busview invalid, busbreakerview unset, new network each time + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + vl = networkVoltageLevelSupplier.get(); + vl.getBusView().getBuses(); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + setAndCheckBusViewBus(vl, busviewbusid, busbreakerviewbusids, getter, setter); + } + + if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) { + // this test of initiliazed busbreakeriewbus only makes sense for nodebreaker topology, + // for busbreakertopology it's the same as all views uninitialized + + // test with busbreakerview invalid, busview unset + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + vl = networkVoltageLevelSupplier.get(); + vl.getBusBreakerView().getBuses(); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + setAndCheckBusViewBus(vl, busviewbusid, busbreakerviewbusids, getter, setter); + } + } + + // test with views that are not initialized at all, new network each time + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + vl = networkVoltageLevelSupplier.get(); + setAndCheckBusViewBus(vl, busviewbusid, busbreakerviewbusids, getter, setter); + } + + // test with views that are initialized and everything set to a real value, reuse network. + // With invalid views it should not propagate to the other view + // TODO currently we have this behavior between the 2 views to + // mimic the behavior when only the same view is set (values are not carried over + // (when the new view is computed after invalidating it). But if we change this behavior, + // we may change the behavior when there are two views to mimic it, and so we should invert this test. + // test one same view, nodebreakertopology and busbreakertopology behave the same here + if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) { + vl = networkVoltageLevelSupplier.get(); + setAllBvb(vl, 1.0, setter); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + for (Bus bvb : vl.getBusView().getBuses()) { + assertTrue("case " + bvb.getId() + " (busbreakerviewbus) is set, should not have been carried over from invalid previous view", + Double.isNaN(getter.apply(bvb))); + } + + // test one other view + vl = networkVoltageLevelSupplier.get(); + setAllBvb(vl, 1.0, setter); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + for (Bus bvb : vl.getBusView().getBuses()) { + assertTrue("case " + bvb.getId() + " (busbreakerviewbus) is set, should not have been carried over from invalid previous view", + Double.isNaN(getter.apply(bvb))); + } + } else { // BUS_BREAKER + // No need to test the busbreakerview in busbreakertopology, + // it's not a view it's directly the configured buses. + // so test only with busview view + // NOTE: for busbreakertopology we keep the previous values. + vl = networkVoltageLevelSupplier.get(); + setAllBvb(vl, 1.0, setter); + ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); + for (Bus bvb : vl.getBusView().getBuses()) { + assertEquals("case busbreakertopology " + bvb.getId() + " (busbreakerviewbus) is not set but should be copied from configured buses", + 1, getter.apply(bvb), 0.0); + } + } + } + + private void setAllBbvbAndBvb(VoltageLevel vl, double value, BiConsumer setter) { + // order doesn't matter when we set everything, some things + // are set twice but it doesn't matter. + setAllBbvb(vl, value, setter); + setAllBvb(vl, value, setter); + } + + private void setAllBvb(VoltageLevel vl, double value, BiConsumer setter) { + for (Bus bvb : vl.getBusView().getBuses()) { + setter.accept(bvb, value); + } + } + + private void setAllBbvb(VoltageLevel vl, double value, BiConsumer setter) { + for (Bus bbvb : vl.getBusBreakerView().getBuses()) { + setter.accept(bbvb, value); + } + } + + private void setAndCheckBusViewBus(VoltageLevel vl, String busviewbusid, List busbreakerviewbusids, Function getter, BiConsumer setter) { + setter.accept(vl.getBusView().getBus(busviewbusid), 1.); + for (String busbreakerviewbusid : busbreakerviewbusids) { + assertEquals("case " + busviewbusid + " (busviewbus) is set, " + busbreakerviewbusid + " (busbreakerviewbus) should have been set", + 1, getter.apply(vl.getBusBreakerView().getBus(busbreakerviewbusid)), 0); + } + for (Bus bvb : vl.getBusView().getBuses()) { + if (!busviewbusid.equals(bvb.getId())) { + assertTrue("case " + busviewbusid + " (busviewbus) is set, " + bvb.getId() + " (busviewbus) should not have been set", + Double.isNaN(getter.apply(bvb))); + } + } + for (Bus bbvb : vl.getBusBreakerView().getBuses()) { + if (!busbreakerviewbusids.contains(bbvb.getId())) { + assertTrue("case " + busviewbusid + " (busviewbus) is set, " + bbvb.getId() + " (busbreakerviewbus) should not have been set", + Double.isNaN(getter.apply(bbvb))); + } + } + } + + private void setAndCheckBusBreakerBus(VoltageLevel vl, String busbreakerviewbusid, String busviewbusid, Function getter, BiConsumer setter) { + setter.accept(vl.getBusBreakerView().getBus(busbreakerviewbusid), 1.); + if (!busviewbusid.isEmpty()) { + assertEquals("case " + busbreakerviewbusid + " (busbreakerviewbus) is set, " + busviewbusid + " (busviewbus) should have been set", + 1, getter.apply(vl.getBusView().getBus(busviewbusid)), 0); + } + for (Bus bbvb : vl.getBusBreakerView().getBuses()) { + if (!busbreakerviewbusid.equals(bbvb.getId())) { + assertTrue("case " + busbreakerviewbusid + " (busbreakerviewbus) is set, " + bbvb.getId() + " (busbreakerviewbus) should not have been set", + Double.isNaN(getter.apply(bbvb))); + } + } + for (Bus bvb : vl.getBusView().getBuses()) { + if (!busviewbusid.equals(bvb.getId())) { + assertTrue("case " + busbreakerviewbusid + "(busbreakerviewbus) is set, " + bvb.getId() + " (busviewbus) should not have been set", + Double.isNaN(getter.apply(bvb))); + } + } + } + + // Setting all busbreakerviewbus connected in the busview to get deterministic behavior + private void setAndCheckBusBreakerBusDeterministic(VoltageLevel vl, List busbreakerviewbusids, String busviewbusid, Function getter, BiConsumer setter) { + for (String busbreakerviewbusid : busbreakerviewbusids) { + setter.accept(vl.getBusBreakerView().getBus(busbreakerviewbusid), 1.); + } + if (!busviewbusid.isEmpty()) { + assertEquals("case " + busbreakerviewbusids + " (busbreakerviewbus) is set, " + busviewbusid + " (busviewbus) should have been set", + 1, getter.apply(vl.getBusView().getBus(busviewbusid)), 0); + } + for (Bus bbvb : vl.getBusBreakerView().getBuses()) { + if (!busbreakerviewbusids.contains(bbvb.getId())) { + assertTrue("case " + busbreakerviewbusids + " (busbreakerviewbus) is set, " + bbvb.getId() + " (busbreakerviewbus) should not have been set", + Double.isNaN(getter.apply(bbvb))); + } + } + for (Bus bvb : vl.getBusView().getBuses()) { + if (!busviewbusid.equals(bvb.getId())) { + assertTrue("case " + busbreakerviewbusids + "(busbreakerviewbus) is set, " + bvb.getId() + " (busviewbus) should not have been set", + Double.isNaN(getter.apply(bvb))); + } + } + } + + private static Map> invertMap(Map map) { + return map + .entrySet().stream() + .filter(x -> !x.getValue().isEmpty()) + .collect(Collectors.groupingBy(Map.Entry::getValue, TreeMap::new, Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); + } +} diff --git a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelSetVTest.java b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelSetVTest.java deleted file mode 100644 index be0b81c06..000000000 --- a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelSetVTest.java +++ /dev/null @@ -1,336 +0,0 @@ -/** - * Copyright (c) 2020, 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.network.store.iidm.impl; - -import com.powsybl.iidm.network.Bus; -import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.SwitchKind; -import com.powsybl.iidm.network.TopologyKind; -import com.powsybl.iidm.network.VoltageLevel; -import org.junit.Test; - -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * @author Slimane Amar - */ -public class VoltageLevelSetVTest { - @Test - public void testWithMultipleBusInBusBreakerAndBusView() { - testSetVMultipleBusAcrossViews(() -> { - Network network = Network.create("test_mcc", "test"); - return createNodeBreaker(network); - }, NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS, NBVL_BUSVIEWBUS_TO_BUSBREAKERVIEWBUS); - testSetVMultipleBusAcrossViews(() -> { - Network network = Network.create("test_mcc", "test"); - return createBusBreaker(network); - }, BBVL_CONFIGUREDBUS_TO_BUSVIEWBUS, BBVL_BUSVIEWBUS_TO_CONFIGUREDBUS); - } - - private static final Map NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS = new TreeMap<>(Map.of( - "VL1_0", "VL1_0", - "VL1_2", "VL1_0", - "VL1_3", "VL1_3", - "VL1_4", "VL1_4", - "VL1_10", "", - "VL1_11", "VL1_0", - "VL1_5", "", - "VL1_12", "", - "VL1_13", "", - "VL1_14", "" - )); - private static final Map> NBVL_BUSVIEWBUS_TO_BUSBREAKERVIEWBUS = invertMap(NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS); - - private static VoltageLevelImpl createNodeBreaker(Network network) { - // nodebreaker topology voltage level - VoltageLevel vl1 = network.newVoltageLevel() - .setId("VL1") - .setNominalV(225.0) - .setTopologyKind(TopologyKind.NODE_BREAKER) - .add(); - vl1.getNodeBreakerView().newBusbarSection() - .setId("BBS1") - .setNode(0) - .add(); - vl1.getNodeBreakerView().newSwitch() - .setId("VL1SW1") - .setKind(SwitchKind.BREAKER) - .setRetained(false) - .setOpen(false) - .setNode1(0) - .setNode2(1) - .add(); - vl1.getNodeBreakerView().newBusbarSection() - .setId("BBS2") - .setNode(1) - .add(); - vl1.getNodeBreakerView().newSwitch() - .setId("VL1SW2") - .setKind(SwitchKind.BREAKER) - .setRetained(true) - .setOpen(false) - .setNode1(1) - .setNode2(2) - .add(); - vl1.getNodeBreakerView().newBusbarSection() - .setId("BBS3") - .setNode(2) - .add(); - vl1.getNodeBreakerView().newSwitch() - .setId("VL1SW3") - .setKind(SwitchKind.BREAKER) - .setRetained(true) - .setOpen(true) - .setNode1(2) - .setNode2(3) - .add(); - vl1.getNodeBreakerView().newBusbarSection() - .setId("BBS4") - .setNode(3) - .add(); - vl1.getNodeBreakerView().newSwitch() - .setId("VL1SW4") - .setKind(SwitchKind.BREAKER) - .setRetained(false) - .setOpen(true) - .setNode1(3) - .setNode2(4) - .add(); - vl1.getNodeBreakerView().newBusbarSection() - .setId("BBS5") - .setNode(4) - .add(); - vl1.getNodeBreakerView().newSwitch() - .setId("VL1SW5") - .setKind(SwitchKind.BREAKER) - .setRetained(true) - .setOpen(true) - .setNode1(4) - .setNode2(5) - .add(); - vl1.getNodeBreakerView().newBusbarSection() - .setId("BBS6") - .setNode(5) - .add(); - - // add loads so that buses are not pruned - vl1.getNodeBreakerView().newInternalConnection() - .setNode1(0) - .setNode2(6) - .add(); - vl1.newLoad().setId("VL1L1").setNode(6).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); - - vl1.getNodeBreakerView().newInternalConnection() - .setNode1(2) - .setNode2(7) - .add(); - vl1.newLoad().setId("VL1L2").setNode(7).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); - - vl1.getNodeBreakerView().newInternalConnection() - .setNode1(3) - .setNode2(8) - .add(); - vl1.newLoad().setId("VL1L3").setNode(8).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); - - vl1.getNodeBreakerView().newInternalConnection() - .setNode1(4) - .setNode2(9) - .add(); - vl1.newLoad().setId("VL1L4").setNode(9).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); - - //no load on BBS6, it will be pruned - - //add a load on bbs1 but not connected, it will be in a separate bus in the busbreakerview and pruned in the busview - vl1.getNodeBreakerView().newSwitch() - .setId("VL1SW6") - .setKind(SwitchKind.BREAKER) - .setRetained(false) - .setOpen(true) - .setNode1(0) - .setNode2(10) - .add(); - vl1.newLoad().setId("VL1L5").setNode(10).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); - - //add a load on bbs1 but with retained switch, it will be in a separate bus in the busbreakerview but connected in the busview - vl1.getNodeBreakerView().newSwitch() - .setId("VL1SW7") - .setKind(SwitchKind.BREAKER) - .setRetained(true) - .setOpen(false) - .setNode1(0) - .setNode2(11) - .add(); - vl1.newLoad().setId("VL1L6").setNode(11).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); - - // add busbar section BBS7 with 3 retained switches between BBS7 and BBS6, so there are buses in bus breaker view - // between 2 retained switches and without equipment - vl1.getNodeBreakerView().newBusbarSection() - .setId("BBS7") - .setNode(12) - .add(); - vl1.getNodeBreakerView().newSwitch() - .setId("VL1SW8") - .setKind(SwitchKind.DISCONNECTOR) - .setRetained(true) - .setOpen(false) - .setNode1(5) - .setNode2(13) - .add(); - vl1.getNodeBreakerView().newSwitch() - .setId("VL1SW9") - .setKind(SwitchKind.DISCONNECTOR) - .setRetained(true) - .setOpen(false) - .setNode1(12) - .setNode2(14) - .add(); - vl1.getNodeBreakerView().newSwitch() - .setId("VL1SW10") - .setKind(SwitchKind.BREAKER) - .setRetained(true) - .setOpen(false) - .setNode1(13) - .setNode2(14) - .add(); - - return (VoltageLevelImpl) vl1; - } - - private static final Map BBVL_CONFIGUREDBUS_TO_BUSVIEWBUS = new TreeMap<>(Map.of( - "BUS1", "VL2_0", - "BUS2", "VL2_0", - "BUS3", "VL2_1", - "BUS4", "VL2_2", - "BUS5", "" - )); - - private static final Map> BBVL_BUSVIEWBUS_TO_CONFIGUREDBUS = invertMap(BBVL_CONFIGUREDBUS_TO_BUSVIEWBUS); - - private static VoltageLevelImpl createBusBreaker(Network network) { - // busbreaker topology voltage level - VoltageLevel vl2 = network.newVoltageLevel() - .setId("VL2") - .setNominalV(225.0) - .setTopologyKind(TopologyKind.BUS_BREAKER) - .add(); - vl2.getBusBreakerView().newBus() - .setId("BUS1") - .add(); - vl2.getBusBreakerView().newSwitch() - .setId("VL2SW1") - .setOpen(false) - .setBus1("BUS1") - .setBus2("BUS2") - .add(); - vl2.getBusBreakerView().newBus() - .setId("BUS2") - .add(); - vl2.getBusBreakerView().newSwitch() - .setId("VL2SW2") - .setOpen(true) - .setBus1("BUS2") - .setBus2("BUS3") - .add(); - vl2.getBusBreakerView().newBus() - .setId("BUS3") - .add(); - vl2.getBusBreakerView().newBus() - .setId("BUS4") - .add(); - vl2.getBusBreakerView().newBus() - .setId("BUS5") - .add(); - - // loads to avoid pruning - vl2.newLoad().setId("VL2L1").setBus("BUS1").setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); - vl2.newLoad().setId("VL2L2").setBus("BUS3").setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); - vl2.newLoad().setId("VL2L3").setBus("BUS4").setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); - vl2.newLoad().setId("VL2L4").setConnectableBus("BUS5").setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); - // BUS2 has no load it will be pruned - - return (VoltageLevelImpl) vl2; - } - - private void testSetVMultipleBusAcrossViews(Supplier supplier, Map busBreakerViewBusToBusViewBus, Map> busViewBusToBusBreakerViewBus) { - - VoltageLevel vl = supplier.get(); - for (Map.Entry entry : busBreakerViewBusToBusViewBus.entrySet()) { - String busbreakerviewbusid = entry.getKey(); - String busviewbusid = entry.getValue(); - - for (Bus bbvb : vl.getBusBreakerView().getBuses()) { - bbvb.setV(Double.NaN); - } - for (Bus bvb : vl.getBusView().getBuses()) { - bvb.setV(Double.NaN); - } - - vl.getBusBreakerView().getBus(busbreakerviewbusid).setV(1.); - if (!busviewbusid.isEmpty()) { - assertEquals("case " + busbreakerviewbusid + " (busbreakerviewbus) is set, " + busviewbusid + " (busviewbus) should have been set", - 1, vl.getBusView().getBus(busviewbusid).getV(), 0); - } - for (Bus bbvb : vl.getBusBreakerView().getBuses()) { - if (!busbreakerviewbusid.equals(bbvb.getId())) { - assertTrue("case " + busbreakerviewbusid + " (busbreakerviewbus) is set, " + bbvb.getId() + " (busbreakerviewbus) should not have been set", - Double.isNaN(bbvb.getV())); - } - } - for (Bus bvb : vl.getBusView().getBuses()) { - if (!busviewbusid.equals(bvb.getId())) { - assertTrue("case " + busbreakerviewbusid + "(busbreakerviewbus) is set, " + bvb.getId() + " (busviewbus) should not have been set", - Double.isNaN(bvb.getV())); - } - } - } - - vl = supplier.get(); - for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { - String busviewbusid = entry.getKey(); - List busbreakerviewbusids = entry.getValue(); - - for (Bus bbvb : vl.getBusBreakerView().getBuses()) { - bbvb.setV(Double.NaN); - } - for (Bus bvb : vl.getBusView().getBuses()) { - bvb.setV(Double.NaN); - } - - vl.getBusView().getBus(busviewbusid).setV(1.); - for (String busbreakerviewbusid : busbreakerviewbusids) { - assertEquals("case " + busviewbusid + " (busviewbus) is set, " + busbreakerviewbusid + " (busbreakerviewbus) should have been set", - 1, vl.getBusBreakerView().getBus(busbreakerviewbusid).getV(), 0); - } - for (Bus bvb : vl.getBusView().getBuses()) { - if (!busviewbusid.equals(bvb.getId())) { - assertTrue("case " + busviewbusid + " (busviewbus) is set, " + bvb.getId() + " (busviewbus) should not have been set", - Double.isNaN(bvb.getV())); - } - } - for (Bus bbvb : vl.getBusBreakerView().getBuses()) { - if (!busbreakerviewbusids.contains(bbvb.getId())) { - assertTrue("case " + busviewbusid + " (busviewbus) is set, " + bbvb.getId() + " (busbreakerviewbus) should not have been set", - Double.isNaN(bbvb.getV())); - } - } - } - } - - private static Map> invertMap(Map map) { - return map - .entrySet().stream() - .filter(x -> !x.getValue().isEmpty()) - .collect(Collectors.groupingBy(Map.Entry::getValue, TreeMap::new, Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); - } -} diff --git a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelTest.java b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelTest.java index 673191a89..5fa12bd0c 100644 --- a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelTest.java +++ b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelTest.java @@ -33,6 +33,7 @@ public void testBusBreakerSetVUpdateVoltageLevel() { assertEquals("Voltage should match in BusBreakerView after update in BusView", 222, l1.getTerminal1().getBusBreakerView().getBus().getV(), 0.0); // Set voltage using BusBreakerView configured bus, should set the existing cache of the BusView calculated bus immediately + // deterministic when the cache is existing l1.getTerminal1().getBusBreakerView().getBus().setV(400.0); // Verify voltage update in pre-existing cached BusView calculated bus @@ -50,6 +51,7 @@ public void testNodeBreakerSetVUpdateVoltageLevel() { LineImpl l1 = (LineImpl) network.getLine("L1"); // Update voltage using BusBreakerView calculated bus, with no cache for BusView calculated bus so no immediate update. + // non deterministic in general but this test happens to have a one to one mapping between the busview and the busbreakerview l1.getTerminal1().getBusBreakerView().getBus().setV(222); // Verify the voltage update in BusView calculated bus, here it should getV from BusBreakerView calculated bus when creating the cache @@ -72,7 +74,9 @@ public void testBusBreakerSetAngleUpdateVoltageLevel() { LineImpl l1 = (LineImpl) network.getLine("L1"); // Update angle using BusBreakerView configured bus, with no cache for BusView calculated bus so no immediate update. - l1.getTerminal1().getBusBreakerView().getBus().setAngle(111); + // non deterministic, so we need to update all buses which are actually connected electrically together + // In this test, all switches in vl1 are closed so update all the buses + l1.getTerminal1().getVoltageLevel().getBusBreakerView().getBuses().forEach(bus -> bus.setAngle(111)); // Verify the angle update in BusView calculated bus, here it should getAngle from BusBreakerView configured bus when creating the cache assertEquals("Angle should match in BusView after update in BusBreakerView", 111, l1.getTerminal1().getBusView().getBus().getAngle(), 0.0); @@ -101,6 +105,7 @@ public void testNodeBreakerSetAngleUpdateVoltageLevel() { assertEquals("Angle should match in BusBreakerView after second update in BusView", 222, l1.getTerminal1().getBusBreakerView().getBus().getAngle(), 0.0); // Set angle using BusBreakerView calculated bus, should also setAngle in the existing BusView calculated bus cache immediately + // deterministic when the cache is existing l1.getTerminal1().getBusBreakerView().getBus().setAngle(400.0); // Verify angle update in BusBreakerView