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 4210c0c12..4591a2735 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 @@ -9,6 +9,8 @@ import com.powsybl.commons.config.PlatformConfig; import com.powsybl.iidm.network.*; import com.powsybl.network.store.model.*; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; import org.jgrapht.Graph; import org.jgrapht.alg.connectivity.ConnectivityInspector; import org.jgrapht.graph.DirectedPseudograph; @@ -358,6 +360,62 @@ private void setCalculatedBuses(Resource voltageLevelRes } } + private CalculatedBusAttributes findFirstMatchingNodeBreakerCalculatedBusAttributes(Resource voltageLevelResource, + ConnectedSetResult connectedSet, boolean isBusView) { + // TODO Some day we may decide to start preserving phase/angle values + // in nodebreaker topology even after invalidating the views, so we + // could remove the check for isCalculatedBusesValid. Here it controls + // whether we preserve or not the phase/angle values accross the other + // view. For now we do not preserve to be consistent with the behavior + // of not preserving values from the same view after invalidation. + List calculatedBusAttributesInOtherView = isBusView ? voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView() : voltageLevelResource.getAttributes().getCalculatedBusesForBusView(); + Map nodesToCalculatedBusesInOtherView = isBusView ? voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusBreakerView() : voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusView(); + Set nodes = (Set) connectedSet.getConnectedNodesOrBuses(); + if (voltageLevelResource.getAttributes().isCalculatedBusesValid() + && !CollectionUtils.isEmpty(calculatedBusAttributesInOtherView) + && !MapUtils.isEmpty(nodesToCalculatedBusesInOtherView) + && !nodes.isEmpty()) { + // busNumInOtherView is deterministic for the busbreakerview because all busbreakerviewbuses correspond + // to the same busviewbus. For the busview, busNumInOtherView will be non deterministic, it will + // be one of the busbreakerbuses of this busviewbus. + Integer node = nodes.iterator().next(); + Integer busNumInOtherView = nodesToCalculatedBusesInOtherView.get(node); + if (busNumInOtherView != null) { + return calculatedBusAttributesInOtherView.get(busNumInOtherView); + } + } + 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 phase/angle values are preserved + // when set in the busbreakerview which is in a sense always valid. + // So mimic the behavior and always preserve them also in the busview + // by *not* testing for isCalculatedBusesValid. + Set configuredBusesIds = (Set) connectedSet.getConnectedNodesOrBuses(); + if (!configuredBusesIds.isEmpty()) { + // nondeterministic, chooses a random configuredbus in this busviewbus + String configuredBusId = configuredBusesIds.iterator().next(); + Bus b = index.getConfiguredBus(configuredBusId).orElseThrow(IllegalStateException::new); + v = b.getV(); + angle = b.getAngle(); + } + } + return new CalculatedBusAttributes(connectedSet.getConnectedVertices(), null, null, v, angle); + } + private CalculationResult getCalculatedBusAttributesList(NetworkObjectIndex index, Resource voltageLevelResource, boolean isBusView) { List calculatedBusAttributesList; Map nodeOrBusToCalculatedBusNum; @@ -369,7 +427,13 @@ private CalculationResult getCalculatedBusAttributesList(NetworkObjectIndex i List> connectedSetList = findConnectedSetList(index, voltageLevelResource, isBusView); calculatedBusAttributesList = connectedSetList .stream() - .map(connectedSet -> new CalculatedBusAttributes(connectedSet.getConnectedVertices(), null, null, Double.NaN, Double.NaN)) + //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 8b2c8b196..e7b516228 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 @@ -15,9 +15,13 @@ import com.powsybl.iidm.network.util.Networks; import com.powsybl.network.store.model.*; import lombok.EqualsAndHashCode; +import org.apache.commons.collections4.CollectionUtils; +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.stream.Collectors; import java.util.stream.Stream; @@ -131,10 +135,35 @@ public double getV() { return getAttributes().getV(); } + private void setVInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) { + calculatedBusAttributes.setV(value); + } + + private void setVInConfiguredBus(ConfiguredBusImpl configuredBus, double value) { + configuredBus.setConfiguredBusV(value); + } + + private void setAngleInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) { + calculatedBusAttributes.setAngle(value); + } + + private void setAngleInConfiguredBus(ConfiguredBusImpl configuredBus, double value) { + configuredBus.setConfiguredBusAngle(value); + } + @Override public Bus setV(double v) { getAttributes().setV(v); index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV); + + if (getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) { + // update V in configured buses + // 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); + } return this; } @@ -147,6 +176,15 @@ public double getAngle() { public Bus setAngle(double angle) { getAttributes().setAngle(angle); index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV); + + if (getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) { + // update angle in configuredBus + // 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); + } return this; } @@ -471,4 +509,62 @@ public , B extends ExtensionAdder> B newExtensi public int getCalculatedBusNum() { return calculatedBusNum; } + + private void updateBusesAttributes(double value, ObjDoubleConsumer setValue) { + // Use the busnum of this bus to get the nodes in this bus to get the + // busnums in the other view to get the buses of the other view to + // update them all. For the busbreakerview, there is only one matching bus in the busview so return early. + // We only update when isCalculatedBusesValid is true, there is no point in updating stale bus objects and + // in when isCalculatedBusesValid is not true, we may even update the wrong buses (but not much of a problem + // because they are stale objects). + // TODO add tests for updates with isCalculatedBusesValid=false + // NOTE: we don't maintain a mapping from busnum to nodes so we iterate + // all the nodes and filter but it should be ok, the number is small. TODO, is this really ok ? + VoltageLevelAttributes vlAttributes = ((VoltageLevelImpl) getVoltageLevel()).getResource().getAttributes(); + Map nodesToCalculatedBuses = isBusView + ? vlAttributes.getNodeToCalculatedBusForBusView() + : vlAttributes.getNodeToCalculatedBusForBusBreakerView(); + Map nodesToCalculatedBusesInOtherView = isBusView + ? vlAttributes.getNodeToCalculatedBusForBusBreakerView() + : vlAttributes.getNodeToCalculatedBusForBusView(); + List calculatedBusAttributes = isBusView + ? vlAttributes.getCalculatedBusesForBusBreakerView() + : vlAttributes.getCalculatedBusesForBusView(); + if (vlAttributes.isCalculatedBusesValid() && !CollectionUtils.isEmpty(calculatedBusAttributes) + && !MapUtils.isEmpty(nodesToCalculatedBuses) && !MapUtils.isEmpty(nodesToCalculatedBusesInOtherView)) { + Set seen = new HashSet<>(); + for (Entry entry : nodesToCalculatedBuses.entrySet()) { + if (getCalculatedBusNum() == entry.getValue()) { + int node = entry.getKey(); + Integer busNumInOtherView = nodesToCalculatedBusesInOtherView.get(node); + if (busNumInOtherView != null && !seen.contains(busNumInOtherView)) { + setValue.accept(calculatedBusAttributes.get(busNumInOtherView), value); + index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV); + seen.add(busNumInOtherView); + } + if (!isBusView) { + return; + } + } + } + } + } + + private void updateConfiguredBuses(double newValue, + ObjDoubleConsumer setValue) { + // update all the configured buses + // NOTE: we don't maintain a mapping from busnum to bus so we iterate + // all the buses and filter but it should be ok, the number is small. TODO, is this really ok ? + // We only update when isCalculatedBusesValid is true, otherwise we may update the wrong configured bus + // TODO add tests for updates with isCalculatedBusesValid=false + VoltageLevelAttributes vlAttributes = ((VoltageLevelImpl) getVoltageLevel()).getResource().getAttributes(); + if (vlAttributes.isCalculatedBusesValid()) { + for (Entry entry : vlAttributes.getBusToCalculatedBusForBusView().entrySet()) { + if (getCalculatedBusNum() == entry.getValue()) { + ConfiguredBusImpl bus = index.getConfiguredBus(entry.getKey()).orElseThrow(IllegalStateException::new); + setValue.accept(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 f5c835937..192f0b7f5 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 @@ -25,11 +25,18 @@ import com.powsybl.iidm.network.ValidationException; import com.powsybl.iidm.network.VoltageLevel; import com.powsybl.iidm.network.VscConverterStation; +import com.powsybl.network.store.model.AttributeFilter; +import com.powsybl.network.store.model.CalculatedBusAttributes; import com.powsybl.network.store.model.ConfiguredBusAttributes; +import com.powsybl.network.store.model.VoltageLevelAttributes; import com.powsybl.network.store.model.Resource; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.collections4.CollectionUtils; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.function.ObjDoubleConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -77,31 +84,97 @@ public double getAngle() { return getResource().getAttributes().getAngle(); } - @Override - public Bus setV(double v) { - if (v < 0) { - throw new ValidationException(this, "voltage cannot be < 0"); - } + 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); + + if (updateCalculatedBus) { + // update V for bus in BusView + updateCalculatedBusAttributes(v, getResource().getAttributes().getVoltageLevelId(), this::setVInCalculatedBus); + } } - return this; + } + + // 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 - public Bus setAngle(double angle) { + public Bus setV(double v) { + if (v < 0) { + throw new ValidationException(this, "voltage cannot be < 0"); + } + setV(v, true); + return this; + } + + 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); + + 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(IllegalArgumentException::new); + VoltageLevelAttributes vlAttributes = voltageLevel.getResource().getAttributes(); + Map calculatedBuses = vlAttributes.getBusToCalculatedBusForBusView(); + List busViewCalculatedBusesAttributes = vlAttributes.getCalculatedBusesForBusView(); + // We only update when isCalculatedBusesValid is true, there is no point in updating stale bus objects and + // in when isCalculatedBusesValid is not true, we may even update the wrong buses (but not much of a problem + // because they are stale objects). + // TODO add tests for updates with isCalculatedBusesValid=false + if (vlAttributes.isCalculatedBusesValid() + && !CollectionUtils.isEmpty(busViewCalculatedBusesAttributes) + && !MapUtils.isEmpty(calculatedBuses)) { + Integer busviewnum = calculatedBuses.get(getId()); + if (busviewnum != null) { + CalculatedBusAttributes busViewCalculatedBusAttributes = busViewCalculatedBusesAttributes.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(busViewCalculatedBusAttributes, 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 81b4248d0..841e2289c 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 @@ -13,14 +13,10 @@ import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.ConnectablePosition; import com.powsybl.iidm.network.extensions.ConnectablePositionAdder; - -import com.powsybl.network.store.model.LimitsAttributes; -import com.powsybl.network.store.model.TemporaryLimitAttributes; import org.junit.Test; import java.util.List; import java.util.Properties; -import java.util.TreeMap; import java.util.stream.Collectors; import static org.junit.Assert.*; @@ -33,58 +29,6 @@ * @author Etienne Homer */ public class LineTest { - //TODO: there is a similar test in the TCK tests. A CurrentLimitsTest extends AbstractCurrentLimitsTest should be created and this test can be deleted. - // The TCK test doesn't pass yet. As is, the network-store implementation of setV(v) on buses is not consistent. We have problems with the views we are working on (BusBreakerView or BusView). - @Test - public void isOverloadedTest() { - Network network = CreateNetworksUtil.createNodeBreakerNetworkWithLine(); - LineImpl l1 = (LineImpl) network.getLine("L1"); - l1.getTerminal1().setP(10); - l1.getTerminal1().setQ(0); - l1.getTerminal1().getBusView().getBus().setV(400.0); - assertFalse(l1.isOverloaded()); - - l1.getTerminal1().setP(400); - - l1.setCurrentLimits(TwoSides.ONE, new LimitsAttributes("PermaLimit1", 600, null), "PermaLimit1"); - assertNull(l1.getNullableCurrentLimits1()); - l1.setSelectedOperationalLimitsGroup1("PermaLimit1"); - assertTrue(l1.getNullableCurrentLimits1().getTemporaryLimits().isEmpty()); - assertFalse(l1.isOverloaded()); - - l1.newCurrentLimits1().setPermanentLimit(50).add(); - assertTrue(l1.isOverloaded()); - - TreeMap temporaryLimits = new TreeMap<>(); - temporaryLimits.put(5, TemporaryLimitAttributes.builder().name("TempLimit5").value(1000).acceptableDuration(5).fictitious(false).build()); - l1.setCurrentLimits(TwoSides.ONE, new LimitsAttributes("PermaLimit1", 40, temporaryLimits), "PermaLimit1"); - l1.setCurrentLimits(TwoSides.TWO, new LimitsAttributes("PermaLimit1", 40, temporaryLimits), "PermaLimit1"); - l1.setSelectedOperationalLimitsGroup1("PermaLimit1"); - l1.setSelectedOperationalLimitsGroup2("PermaLimit1"); - assertEquals(5, l1.getOverloadDuration()); - - assertTrue(l1.checkPermanentLimit(TwoSides.ONE, LimitType.CURRENT)); - assertTrue(l1.checkPermanentLimit1(LimitType.CURRENT)); - assertFalse(l1.checkPermanentLimit(TwoSides.TWO, LimitType.CURRENT)); - assertFalse(l1.checkPermanentLimit2(LimitType.CURRENT)); - assertFalse(l1.checkPermanentLimit(TwoSides.ONE, LimitType.APPARENT_POWER)); - assertFalse(l1.checkPermanentLimit(TwoSides.TWO, LimitType.ACTIVE_POWER)); - assertThrows(UnsupportedOperationException.class, () -> l1.checkPermanentLimit(TwoSides.TWO, LimitType.VOLTAGE)); - - Overload overload = l1.checkTemporaryLimits(TwoSides.ONE, LimitType.CURRENT); - assertEquals("TempLimit5", overload.getTemporaryLimit().getName()); - assertEquals(40.0, overload.getPreviousLimit(), 0); - assertEquals(5, overload.getTemporaryLimit().getAcceptableDuration()); - assertNull(l1.checkTemporaryLimits(TwoSides.TWO, LimitType.CURRENT)); - - temporaryLimits.put(5, TemporaryLimitAttributes.builder().name("TempLimit5").value(20).acceptableDuration(5).fictitious(false).build()); - assertEquals(Integer.MAX_VALUE, l1.getOverloadDuration()); - - temporaryLimits.put(10, TemporaryLimitAttributes.builder().name("TempLimit10").value(8).acceptableDuration(10).fictitious(false).build()); - // check duration sorting order: first entry has the highest duration - assertEquals(10., l1.getNullableCurrentLimits1().getTemporaryLimits().iterator().next().getAcceptableDuration(), 0); - } - @Test public void testAddConnectablePositionExtensionToLine() { Network network = CreateNetworksUtil.createNodeBreakerNetworkWithLine(); 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..7b1e7ca29 --- /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(); + setAllBusBreakerViewBusAndBusViewBus(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(); + setAllBusBreakerViewBus(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(); + setAllBusViewBus(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(); + setAllBusViewBus(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(); + setAllBusBreakerViewBusAndBusViewBus(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(); + setAllBusViewBus(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(); + setAllBusViewBus(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(); + setAllBusViewBus(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 setAllBusBreakerViewBusAndBusViewBus(VoltageLevel vl, double value, BiConsumer setter) { + // order doesn't matter when we set everything, some things + // are set twice but it doesn't matter. + setAllBusBreakerViewBus(vl, value, setter); + setAllBusViewBus(vl, value, setter); + } + + private void setAllBusViewBus(VoltageLevel vl, double value, BiConsumer setter) { + for (Bus bvb : vl.getBusView().getBuses()) { + setter.accept(bvb, value); + } + } + + private void setAllBusBreakerViewBus(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/VoltageLevelTest.java b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelTest.java index 1ffe9b351..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 @@ -17,6 +17,101 @@ */ public class VoltageLevelTest { + @Test + // For busbreaker topology, + // set in busbreakerview when busview cache exists + // here we use setV and in other tests we use setAngle to test + // all combinations of topology and update order + public void testBusBreakerSetVUpdateVoltageLevel() { + Network network = CreateNetworksUtil.createBusBreakerNetworkWithLine(); + LineImpl l1 = (LineImpl) network.getLine("L1"); + + // Update voltage using BusView calculated bus, should also set the BusBreakerView configured bus immediately + l1.getTerminal1().getBusView().getBus().setV(222); + + // Verify voltage update in BusBreakerView configured bus + 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 + assertEquals("Voltage should match in BusView after update in BusBreakerView", 400.0, l1.getTerminal1().getBusView().getBus().getV(), 0.0); + } + + @Test + // For nodebreaker topology + // set in busbreakerview when busview cache doesn't exist, + // and then set in busview when busbreakerview cache exists + // here we use setV and in other tests we use setAngle to test + // all combinations of topology and update order + public void testNodeBreakerSetVUpdateVoltageLevel() { + Network network = CreateNetworksUtil.createNodeBreakerNetworkWithLine(); + 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 + assertEquals("Voltage should match in BusBreakerView after second update in BusView", 222, l1.getTerminal1().getBusView().getBus().getV(), 0.0); + + // Set voltage using BusView calculated bus, should setV in the existing BusBreakerView calculated bus cache immediately + l1.getTerminal1().getBusView().getBus().setV(400.0); + + // Verify voltage update in pre-existing cached BusBreakerView calculated bus + assertEquals("Voltage should match in BusBreakerView after update in BusView", 400.0, l1.getTerminal1().getBusBreakerView().getBus().getV(), 0.0); + } + + @Test + // For busbreaker topology, + // set in busbreakerview when busview cache doesn't exist + // here we use setAngle and in other tests we use setV to test + // all combinations of topology and update order + public void testBusBreakerSetAngleUpdateVoltageLevel() { + Network network = CreateNetworksUtil.createBusBreakerNetworkWithLine(); + LineImpl l1 = (LineImpl) network.getLine("L1"); + + // Update angle using BusBreakerView configured bus, with no cache for BusView calculated bus so no immediate update. + // 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); + + // Set angle using BusView calculated bus, should setAngle in the BusBreakerView configured bus immediately + l1.getTerminal1().getBusView().getBus().setAngle(400.0); + + // Verify Angle update in BusBreakerView configured bus + assertEquals("Angle should match in BusView after update in BusBreakerView", 400.0, l1.getTerminal1().getBusView().getBus().getAngle(), 0.0); + } + + @Test + // For nodebreaker topology + // set in busiew when busbreakervview cache doesn't exist, + // and then set in busbreakerview when busview cache exists + // here we use setAngle and in other tests we use setV to test + // all combinations of topology and update order + public void testNodeBreakerSetAngleUpdateVoltageLevel() { + Network network = CreateNetworksUtil.createNodeBreakerNetworkWithLine(); + LineImpl l1 = (LineImpl) network.getLine("L1"); + + // Update angle using BusView calculated bus, with no cache for BusBreakerView calculated bus so no immediate update. + l1.getTerminal1().getBusView().getBus().setAngle(222); + + // Verify the angle update in BusBreakerView calculated bus, here it should getV from BusView calculated bus when creating the cache + 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 + assertEquals("Angle should match in BusBreakerView after update in BusView", 400.0, l1.getTerminal1().getBusBreakerView().getBus().getAngle(), 0.0); + } + @Test public void testBusBreakerConnectables() { Network network = CreateNetworksUtil.createBusBreakerNetworkWithLine(); diff --git a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/tck/CurrentLimitsTest.java b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/tck/CurrentLimitsTest.java index d8644f85b..241ed0831 100644 --- a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/tck/CurrentLimitsTest.java +++ b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/tck/CurrentLimitsTest.java @@ -12,33 +12,4 @@ * @author Geoffroy Jamgotchian */ public class CurrentLimitsTest extends AbstractCurrentLimitsTest { - @Override - public void test() { - // FIXME delete this test when we fix Bus.getV/setV not getting/updating correctly the V in all views - } - - @Override - public void testForThreeWindingsTransformerLeg1() { - // FIXME delete this test when we fix Bus.getV/setV not getting/updating correctly the V in all views - } - - @Override - public void testForThreeWindingsTransformerLeg2() { - // FIXME delete this test when we fix Bus.getV/setV not getting/updating correctly the V in all views - } - - @Override - public void testForThreeWindingsTransformerLeg3() { - // FIXME delete this test when we fix Bus.getV/setV not getting/updating correctly the V in all views - } - - @Override - public void testLimitWithoutTempLimit() { - // FIXME delete this test when we fix Bus.getV/setV not getting/updating correctly the V in all views - } - - @Override - public void testSetterGetter() { - // FIXME delete this test when we fix Bus.getV/setV not getting/updating correctly the V in all views - } } diff --git a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/tck/MainConnectedComponentWithSwitchTest.java b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/tck/MainConnectedComponentWithSwitchTest.java index 9e01d1315..883428e0f 100644 --- a/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/tck/MainConnectedComponentWithSwitchTest.java +++ b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/tck/MainConnectedComponentWithSwitchTest.java @@ -12,9 +12,4 @@ * @author Geoffroy Jamgotchian */ public class MainConnectedComponentWithSwitchTest extends AbstractMainConnectedComponentWithSwitchTest { - - @Override - public void test() { - // FIXME remove this test when we stop losing the v of buses / use the right views - } }