From a43f0021e8f3a178e4bdb071df7de56ebcf8906c Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Fri, 13 Sep 2024 12:38:16 +0200 Subject: [PATCH 01/15] Synchronization between views for v and angle bus values. Signed-off-by: Franck LECUYER --- .../store/iidm/impl/AbstractTopology.java | 70 +++++++++++- .../store/iidm/impl/CalculatedBus.java | 108 +++++++++++++++++- .../store/iidm/impl/ConfiguredBusImpl.java | 45 ++++++++ .../network/store/iidm/impl/LineTest.java | 76 +++--------- .../store/iidm/impl/VoltageLevelTest.java | 72 ++++++++++++ .../iidm/impl/tck/CurrentLimitsTest.java | 29 ----- .../MainConnectedComponentWithSwitchTest.java | 5 - 7 files changed, 307 insertions(+), 98 deletions(-) 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..35f8e170d 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,14 +6,18 @@ */ 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.*; +import org.apache.commons.collections4.CollectionUtils; import org.jgrapht.Graph; import org.jgrapht.alg.connectivity.ConnectivityInspector; import org.jgrapht.graph.DirectedPseudograph; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; /** @@ -358,6 +362,63 @@ private void setCalculatedBuses(Resource voltageLevelRes } } + private void getVAndAngleFromConfiguredBus(NetworkObjectIndex index, + Resource voltageLevelResource, + ConnectedSetResult connectedSet, + AtomicDouble v, + AtomicDouble angle) { + index.getConfiguredBuses(voltageLevelResource.getId()).forEach(bus -> { + ConfiguredBusImpl configuredBus = (ConfiguredBusImpl) bus; + AtomicReference foundConfiguredBus = new AtomicReference<>(); + 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) { + AtomicBoolean foundInCalculatedBuses = new AtomicBoolean(false); + List calculatedBusAttributes = isBusView ? + voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView() : + voltageLevelResource.getAttributes().getCalculatedBusesForBusView(); + if (!CollectionUtils.isEmpty(calculatedBusAttributes)) { + connectedSet.getConnectedVertices().forEach(vertex -> { + List busesInOtherView = calculatedBusAttributes.stream().filter(attr -> attr.getVertices().contains(vertex)).toList(); + if (!CollectionUtils.isEmpty(busesInOtherView)) { + busesInOtherView.forEach(b -> { + if (Double.isNaN(v.get()) && !Double.isNaN(b.getV())) { + v.set(b.getV()); + foundInCalculatedBuses.set(true); + } + if (Double.isNaN(angle.get()) && !Double.isNaN(b.getAngle())) { + angle.set(b.getAngle()); + foundInCalculatedBuses.set(true); + } + }); + } + }); + } + if (isBusView && !foundInCalculatedBuses.get()) { + // get V and Angle values from configured buses + getVAndAngleFromConfiguredBus(index, voltageLevelResource, connectedSet, v, angle); + } + } + private CalculationResult getCalculatedBusAttributesList(NetworkObjectIndex index, Resource voltageLevelResource, boolean isBusView) { List calculatedBusAttributesList; Map nodeOrBusToCalculatedBusNum; @@ -365,11 +426,16 @@ 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); + AtomicDouble v = new AtomicDouble(Double.NaN); + AtomicDouble angle = new AtomicDouble(Double.NaN); calculatedBusAttributesList = connectedSetList .stream() - .map(connectedSet -> new CalculatedBusAttributes(connectedSet.getConnectedVertices(), null, null, Double.NaN, Double.NaN)) + .map(connectedSet -> { + // 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()); + }) .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..eca3470ef 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,10 +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 java.util.*; 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; @@ -48,6 +51,9 @@ 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); @@ -131,13 +137,94 @@ public double getV() { return getAttributes().getV(); } + private void setVInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) { + calculatedBusAttributes.setV(value); + } + + private void setVInConfiguredBus(ConfiguredBusAttributes configuredBusAttributes, double value) { + configuredBusAttributes.setV(value); + } + + private double getVInBus(Bus bus) { + return bus.getV(); + } + + 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(); + } + @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 + updateConfiguredBuses(v, getAttributes(), VOLTAGE, this::getVInBus, this::setVInConfiguredBus); + } else { + if (isBusView) { + // update V for buses in BusBreakerView + updateBusesAttributes(v, voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView(), getAttributes(), this::setVInCalculatedBus); + } else { + // update V for buses in BusView + updateBusesAttributes(v, voltageLevelResource.getAttributes().getCalculatedBusesForBusView(), getAttributes(), this::setVInCalculatedBus); + } + } return this; } + private void updateBusesAttributes(double value, + List calculatedBusAttributesList, + CalculatedBusAttributes sourceBusAttributes, + ObjDoubleConsumer setValue) { + if (!CollectionUtils.isEmpty(calculatedBusAttributesList)) { + calculatedBusAttributesList.forEach(busToUpdate -> busToUpdate.getVertices().forEach(vertex1 -> + sourceBusAttributes.getVertices().stream().filter(v -> v.getId().equals(vertex1.getId())).findFirst().ifPresent(vertex2 -> { + setValue.accept(busToUpdate, value); + index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV); + }) + )); + } + } + + private void updateConfiguredBuses(double newValue, + CalculatedBusAttributes calculatedBusAttributesBus, + String attributeName, + ToDoubleFunction getValue, + ObjDoubleConsumer setValue) { + List busesIds = calculatedBusAttributesBus.getVertices().stream() + .map(Vertex::getBus) + .toList(); + + List buses = index.getConfiguredBuses().stream() + .filter(bus -> busesIds.contains(bus.getId()) && !Objects.equals(getValue.applyAsDouble(bus), newValue)) + .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(); @@ -147,6 +234,19 @@ 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 + updateConfiguredBuses(angle, getAttributes(), ANGLE, this::getAngleInBus, this::setAngleInConfiguredBus); + } else { + if (isBusView) { + // update angle for Bus in BusBreakerView + updateBusesAttributes(angle, voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView(), getAttributes(), this::setAngleInCalculatedBus); + } else { + // update angle for Bus in BusView + updateBusesAttributes(angle, voltageLevelResource.getAttributes().getCalculatedBusesForBusView(), getAttributes(), this::setAngleInCalculatedBus); + } + } return this; } @@ -423,6 +523,10 @@ 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"); @@ -467,8 +571,4 @@ public , B extends ExtensionAdder> B newExtensi ExtensionAdderProvider provider = ExtensionAdderProviders.findCachedProvider(getImplementationName(), type); return (B) provider.newAdder(this); } - - public int getCalculatedBusNum() { - return calculatedBusNum; - } } 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..f302e2dd4 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,13 +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.Resource; +import org.apache.commons.collections4.CollectionUtils; import java.util.List; import java.util.Optional; +import java.util.function.ObjDoubleConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; /** @@ -77,6 +82,40 @@ public double getAngle() { return getResource().getAttributes().getAngle(); } + 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) { + Optional voltageLevelOpt = index.getVoltageLevel(voltageLevelId); + + voltageLevelOpt.ifPresent(voltageLevel -> { + List calculatedBusAttributesList = voltageLevel.getResource() + .getAttributes() + .getCalculatedBusesForBusView(); + + if (CollectionUtils.isNotEmpty(calculatedBusAttributesList)) { + List calculatedBusesNum = getAllTerminals().stream() + .filter(Terminal::isConnected) + .map(t -> ((CalculatedBus) t.getBusView().getBus()).getCalculatedBusNum()).distinct().toList(); + List busesToUpdateList = IntStream.range(0, calculatedBusAttributesList.size()) + .filter(calculatedBusesNum::contains) + .mapToObj(calculatedBusAttributesList::get) + .toList(); + if (CollectionUtils.isNotEmpty(busesToUpdateList)) { + busesToUpdateList.forEach(b -> setValue.accept(b, newValue)); + index.updateVoltageLevelResource(voltageLevel.getResource(), AttributeFilter.SV); + } + } + }); + } + @Override public Bus setV(double v) { if (v < 0) { @@ -87,6 +126,9 @@ public Bus setV(double v) { 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); } return this; } @@ -98,6 +140,9 @@ public Bus setAngle(double angle) { 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); } return this; } 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 c054defbb..0cac6d2f4 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,32 @@ import com.powsybl.cgmes.conformity.CgmesConformity1Catalog; import com.powsybl.cgmes.conversion.CgmesImport; import com.powsybl.commons.PowsyblException; -import com.powsybl.iidm.network.*; +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.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.*; +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.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -33,58 +45,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/VoltageLevelTest.java b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelTest.java index 1ffe9b351..d78566d66 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,78 @@ */ public class VoltageLevelTest { + @Test + public void testBusBreakerSetVUpdateVoltageLevel() { + Network network = CreateNetworksUtil.createBusBreakerNetworkWithLine(); + LineImpl l1 = (LineImpl) network.getLine("L1"); + + // Update voltage using BusView + l1.getTerminal1().getBusView().getBus().setV(222); + + // Verify the voltage update in BusBreakerView + assertEquals("Voltage should match in BusView after update in BusBreakerView", 222, l1.getTerminal1().getBusBreakerView().getBus().getV(), 0.0); + + // Set voltage using BusBreakerView + l1.getTerminal1().getBusBreakerView().getBus().setV(400.0); + + // Verify voltage update in BusView + assertEquals("Voltage should match in BusView after update in BusBreakerView", 400.0, l1.getTerminal1().getBusView().getBus().getV(), 0.0); + } + + @Test + public void testNodeBreakerSetVUpdateVoltageLevel() { + Network network = CreateNetworksUtil.createNodeBreakerNetworkWithLine(); + LineImpl l1 = (LineImpl) network.getLine("L1"); + + // Update voltage using BusBreakerView + l1.getTerminal1().getBusBreakerView().getBus().setV(222); + + // Verify the voltage update in BusView + assertEquals("Voltage should match in BusBreakerView after second update in BusView", 222, l1.getTerminal1().getBusView().getBus().getV(), 0.0); + + // Set voltage using BusView + l1.getTerminal1().getBusView().getBus().setV(400.0); + + // Verify voltage update in BusBreakerView + assertEquals("Voltage should match in BusBreakerView after update in BusView", 400.0, l1.getTerminal1().getBusBreakerView().getBus().getV(), 0.0); + } + + @Test + public void testBusBreakerSetAngleUpdateVoltageLevel() { + Network network = CreateNetworksUtil.createBusBreakerNetworkWithLine(); + LineImpl l1 = (LineImpl) network.getLine("L1"); + + // Update angle using BusView + l1.getTerminal1().getBusView().getBus().setAngle(111); + + // Verify the angle update in BusBreakerView + assertEquals("Angle should match in BusView after update in BusBreakerView", 111, l1.getTerminal1().getBusBreakerView().getBus().getAngle(), 0.0); + + // Set angle using BusBreakerView + l1.getTerminal1().getBusBreakerView().getBus().setAngle(400.0); + + // Verify Angle update in BusView + assertEquals("Angle should match in BusView after update in BusBreakerView", 400.0, l1.getTerminal1().getBusView().getBus().getAngle(), 0.0); + } + + @Test + public void testNodeBreakerSetAngleUpdateVoltageLevel() { + Network network = CreateNetworksUtil.createNodeBreakerNetworkWithLine(); + LineImpl l1 = (LineImpl) network.getLine("L1"); + + // Update angle using BusBreakerView + l1.getTerminal1().getBusBreakerView().getBus().setAngle(222); + + // Verify the angle update in BusView + assertEquals("Angle should match in BusBreakerView after second update in BusView", 222, l1.getTerminal1().getBusView().getBus().getAngle(), 0.0); + + // Set angle using BusView + l1.getTerminal1().getBusView().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 - } } From 6661c1f80a15ad8f84f7b337bf0f8db0731f84d5 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Tue, 24 Sep 2024 15:14:46 +0200 Subject: [PATCH 02/15] Changes after review Add new test with multiple buses in bus view and bus breaker view in a voltage level Signed-off-by: Franck LECUYER --- .../store/iidm/impl/AbstractTopology.java | 4 +- .../store/iidm/impl/CalculatedBus.java | 6 +- .../store/iidm/impl/ConfiguredBusImpl.java | 18 +- .../store/iidm/impl/VoltageLevelTest.java | 160 +++++++++++++++++- 4 files changed, 171 insertions(+), 17 deletions(-) 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 35f8e170d..67078b01d 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 @@ -401,11 +401,11 @@ private void getVAndAngleFromOtherView(NetworkObjectIndex index, List busesInOtherView = calculatedBusAttributes.stream().filter(attr -> attr.getVertices().contains(vertex)).toList(); if (!CollectionUtils.isEmpty(busesInOtherView)) { busesInOtherView.forEach(b -> { - if (Double.isNaN(v.get()) && !Double.isNaN(b.getV())) { + if (!Double.isNaN(b.getV())) { v.set(b.getV()); foundInCalculatedBuses.set(true); } - if (Double.isNaN(angle.get()) && !Double.isNaN(b.getAngle())) { + if (!Double.isNaN(b.getAngle())) { angle.set(b.getAngle()); foundInCalculatedBuses.set(true); } 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 eca3470ef..4ff2e8d3f 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 @@ -196,15 +196,15 @@ private void updateBusesAttributes(double value, } private void updateConfiguredBuses(double newValue, - CalculatedBusAttributes calculatedBusAttributesBus, + CalculatedBusAttributes calculatedBusAttributes, String attributeName, ToDoubleFunction getValue, ObjDoubleConsumer setValue) { - List busesIds = calculatedBusAttributesBus.getVertices().stream() + List busesIds = calculatedBusAttributes.getVertices().stream() .map(Vertex::getBus) .toList(); - List buses = index.getConfiguredBuses().stream() + List buses = getVoltageLevel().getBusBreakerView().getBusStream() .filter(bus -> busesIds.contains(bus.getId()) && !Objects.equals(getValue.applyAsDouble(bus), newValue)) .toList(); 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 f302e2dd4..c2c75eda5 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 @@ -36,7 +36,6 @@ import java.util.function.ObjDoubleConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; /** @@ -101,17 +100,14 @@ private void updateCalculatedBusAttributes(double newValue, .getCalculatedBusesForBusView(); if (CollectionUtils.isNotEmpty(calculatedBusAttributesList)) { - List calculatedBusesNum = getAllTerminals().stream() + getAllTerminals().stream() .filter(Terminal::isConnected) - .map(t -> ((CalculatedBus) t.getBusView().getBus()).getCalculatedBusNum()).distinct().toList(); - List busesToUpdateList = IntStream.range(0, calculatedBusAttributesList.size()) - .filter(calculatedBusesNum::contains) - .mapToObj(calculatedBusAttributesList::get) - .toList(); - if (CollectionUtils.isNotEmpty(busesToUpdateList)) { - busesToUpdateList.forEach(b -> setValue.accept(b, newValue)); - index.updateVoltageLevelResource(voltageLevel.getResource(), AttributeFilter.SV); - } + .map(t -> ((CalculatedBus) t.getBusView().getBus()).getCalculatedBusNum()) + .findFirst() + .ifPresent(calculatedBusNum -> { + setValue.accept(calculatedBusAttributesList.get(calculatedBusNum), newValue); + index.updateVoltageLevelResource(voltageLevel.getResource(), AttributeFilter.SV); + }); } }); } 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 d78566d66..186f332e3 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 @@ -26,7 +26,7 @@ public void testBusBreakerSetVUpdateVoltageLevel() { l1.getTerminal1().getBusView().getBus().setV(222); // Verify the voltage update in BusBreakerView - assertEquals("Voltage should match in BusView after update in BusBreakerView", 222, l1.getTerminal1().getBusBreakerView().getBus().getV(), 0.0); + assertEquals("Voltage should match in BusBreakerView after update in BusView", 222, l1.getTerminal1().getBusBreakerView().getBus().getV(), 0.0); // Set voltage using BusBreakerView l1.getTerminal1().getBusBreakerView().getBus().setV(400.0); @@ -156,4 +156,162 @@ public void testBusbarSectionPositions() { assertEquals("Busbar section 'idBBS': Busbar index has to be greater or equals to zero", assertThrows(ValidationException.class, busbarSectionPositionAdder::add).getMessage()); } + + @Test + public void testWithMultipleBusInBusBreakerAndBusView() { + Network network = Network.create("test_mcc", "test"); + Substation s1 = network.newSubstation() + .setId("A") + .setCountry(Country.FR) + .add(); + VoltageLevel vl1 = s1.newVoltageLevel() + .setId("B") + .setNominalV(225.0) + .setLowVoltageLimit(0.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + vl1.getNodeBreakerView().newBusbarSection() + .setId("C") + .setNode(0) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("D") + .setKind(SwitchKind.DISCONNECTOR) + .setRetained(false) + .setOpen(false) + .setNode1(0) + .setNode2(1) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("E") + .setKind(SwitchKind.BREAKER) + .setRetained(false) + .setOpen(false) + .setNode1(1) + .setNode2(2) + .add(); + + Substation s2 = network.newSubstation() + .setId("F") + .setCountry(Country.FR) + .add(); + VoltageLevel vl2 = s2.newVoltageLevel() + .setId("G") + .setNominalV(225.0) + .setLowVoltageLimit(0.0) + .setTopologyKind(TopologyKind.NODE_BREAKER) + .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("H") + .setNode(0) + .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("I") + .setNode(1) + .add(); + vl2.getNodeBreakerView().newSwitch() + .setId("J") + .setKind(SwitchKind.DISCONNECTOR) + .setRetained(true) + .setOpen(false) + .setNode1(0) + .setNode2(2) + .add(); + vl2.getNodeBreakerView().newSwitch() + .setId("K") + .setKind(SwitchKind.DISCONNECTOR) + .setRetained(true) + .setOpen(false) + .setNode1(1) + .setNode2(3) + .add(); + vl2.getNodeBreakerView().newSwitch() + .setId("L") + .setKind(SwitchKind.BREAKER) + .setRetained(true) + .setOpen(false) + .setNode1(2) + .setNode2(3) + .add(); + vl2.getNodeBreakerView().newSwitch() + .setId("M") + .setKind(SwitchKind.BREAKER) + .setRetained(false) + .setOpen(false) + .setNode1(0) + .setNode2(4) + .add(); + vl2.getNodeBreakerView().newBusbarSection() + .setId("newBbs1") + .setNode(5) + .add(); + vl2.getNodeBreakerView().newSwitch() + .setId("newDisconnector1") + .setKind(SwitchKind.DISCONNECTOR) + .setRetained(true) + .setOpen(false) + .setNode1(5) + .setNode2(6) + .add(); + vl2.getNodeBreakerView().newSwitch() + .setId("newBreaker1") + .setKind(SwitchKind.BREAKER) + .setRetained(false) + .setOpen(false) + .setNode1(6) + .setNode2(7) + .add(); + vl2.newGenerator() + .setId("newGen1") + .setNode(7) + .setMaxP(100.0) + .setMinP(50.0) + .setTargetP(100.0) + .setTargetV(400.0) + .setVoltageRegulatorOn(true) + .add(); + network.newLine() + .setId("N") + .setR(0.001) + .setX(0.1) + .setG1(0.0) + .setB1(0.0) + .setG2(0.0) + .setB2(0.0) + .setVoltageLevel1("B") + .setNode1(2) + .setVoltageLevel2("G") + .setNode2(4) + .add(); + + // set 2 different V values for the 2 buses in the bus view of voltage level vl2 + assertNotNull(vl2.getBusView().getBus("G_0")); + vl2.getBusView().getBus("G_0").setV(230.); + assertNotNull(vl2.getBusView().getBus("G_5")); + vl2.getBusView().getBus("G_5").setV(250.); + + // 2 buses in the bus breaker view of voltage level vl2 have V value = 230 + assertNotNull(vl2.getBusBreakerView().getBus("G_0")); + assertEquals(230., vl2.getBusBreakerView().getBus("G_0").getV(), 0.0); + assertNotNull(vl2.getBusBreakerView().getBus("G_1")); + assertEquals(230., vl2.getBusBreakerView().getBus("G_1").getV(), 0.0); + + // the 4 other buses in the bus breaker view of voltage level vl2 have V value = 250 + assertNotNull(vl2.getBusBreakerView().getBus("G_2")); + assertEquals(250., vl2.getBusBreakerView().getBus("G_2").getV(), 0.0); + assertNotNull(vl2.getBusBreakerView().getBus("G_3")); + assertEquals(250., vl2.getBusBreakerView().getBus("G_3").getV(), 0.0); + assertNotNull(vl2.getBusBreakerView().getBus("G_5")); + assertEquals(250., vl2.getBusBreakerView().getBus("G_5").getV(), 0.0); + assertNotNull(vl2.getBusBreakerView().getBus("G_6")); + assertEquals(250., vl2.getBusBreakerView().getBus("G_6").getV(), 0.0); + + // set 2 different V values for 2 buses in the bus breaker view of voltage level vl2, which correspond to 2 + // different buses in the bus view + vl2.getBusBreakerView().getBus("G_1").setV(270.); + vl2.getBusBreakerView().getBus("G_5").setV(290.); + + assertEquals(270., vl2.getBusView().getBus("G_0").getV(), 0.0); + assertEquals(290., vl2.getBusView().getBus("G_5").getV(), 0.0); + } } From 3b2640965e56fa8239fd476805ef5177ed20f264 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Tue, 24 Sep 2024 16:20:50 +0200 Subject: [PATCH 03/15] Fix sonar issue Signed-off-by: Franck LECUYER --- .../store/iidm/impl/AbstractTopology.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) 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 67078b01d..cc1e0f5e3 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 @@ -397,21 +397,17 @@ private void getVAndAngleFromOtherView(NetworkObjectIndex index, voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView() : voltageLevelResource.getAttributes().getCalculatedBusesForBusView(); if (!CollectionUtils.isEmpty(calculatedBusAttributes)) { - connectedSet.getConnectedVertices().forEach(vertex -> { - List busesInOtherView = calculatedBusAttributes.stream().filter(attr -> attr.getVertices().contains(vertex)).toList(); - if (!CollectionUtils.isEmpty(busesInOtherView)) { - busesInOtherView.forEach(b -> { - if (!Double.isNaN(b.getV())) { - v.set(b.getV()); - foundInCalculatedBuses.set(true); - } - if (!Double.isNaN(b.getAngle())) { - angle.set(b.getAngle()); - foundInCalculatedBuses.set(true); - } - }); - } - }); + connectedSet.getConnectedVertices().forEach(vertex -> + calculatedBusAttributes.stream().filter(attr -> attr.getVertices().contains(vertex)).forEach(b -> { + if (!Double.isNaN(b.getV())) { + v.set(b.getV()); + foundInCalculatedBuses.set(true); + } + if (!Double.isNaN(b.getAngle())) { + angle.set(b.getAngle()); + foundInCalculatedBuses.set(true); + } + })); } if (isBusView && !foundInCalculatedBuses.get()) { // get V and Angle values from configured buses From 4c2dffbe030db6df938f3cd13c396c2eb0f6269e Mon Sep 17 00:00:00 2001 From: HARPER Jon Date: Fri, 27 Sep 2024 17:43:13 +0200 Subject: [PATCH 04/15] more exhaustive test Signed-off-by: HARPER Jon --- .../store/iidm/impl/VoltageLevelTest.java | 352 ++++++++++++------ 1 file changed, 238 insertions(+), 114 deletions(-) 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 186f332e3..33df20173 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 @@ -9,6 +9,8 @@ import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.BusbarSectionPositionAdder; import org.junit.Test; +import java.util.*; +import java.util.stream.*; import static org.junit.Assert.*; @@ -158,160 +160,282 @@ public void testBusbarSectionPositions() { } @Test + //TODO move all this to a separate file it's huge. public void testWithMultipleBusInBusBreakerAndBusView() { Network network = Network.create("test_mcc", "test"); - Substation s1 = network.newSubstation() - .setId("A") - .setCountry(Country.FR) - .add(); - VoltageLevel vl1 = s1.newVoltageLevel() - .setId("B") + + VoltageLevelImpl vl1 = createNodeBreaker(network); + VoltageLevelImpl vl2 = createBusBreaker(network); + + testSetVMultipleBusAcrossViews(vl1, NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS, NBVL_BUSVIEWBUS_TO_BUSBREAKERVIEWBUS); + testSetVMultipleBusAcrossViews(vl2, 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", "" + )); + 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) - .setLowVoltageLimit(0.0) .setTopologyKind(TopologyKind.NODE_BREAKER) .add(); vl1.getNodeBreakerView().newBusbarSection() - .setId("C") + .setId("BBS1") .setNode(0) .add(); vl1.getNodeBreakerView().newSwitch() - .setId("D") - .setKind(SwitchKind.DISCONNECTOR) + .setId("VL1SW1") + .setKind(SwitchKind.BREAKER) .setRetained(false) .setOpen(false) .setNode1(0) .setNode2(1) .add(); - vl1.getNodeBreakerView().newSwitch() - .setId("E") - .setKind(SwitchKind.BREAKER) - .setRetained(false) - .setOpen(false) - .setNode1(1) - .setNode2(2) - .add(); - - Substation s2 = network.newSubstation() - .setId("F") - .setCountry(Country.FR) - .add(); - VoltageLevel vl2 = s2.newVoltageLevel() - .setId("G") - .setNominalV(225.0) - .setLowVoltageLimit(0.0) - .setTopologyKind(TopologyKind.NODE_BREAKER) - .add(); - vl2.getNodeBreakerView().newBusbarSection() - .setId("H") - .setNode(0) - .add(); - vl2.getNodeBreakerView().newBusbarSection() - .setId("I") + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS2") .setNode(1) .add(); - vl2.getNodeBreakerView().newSwitch() - .setId("J") - .setKind(SwitchKind.DISCONNECTOR) + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW2") + .setKind(SwitchKind.BREAKER) .setRetained(true) .setOpen(false) - .setNode1(0) + .setNode1(1) .setNode2(2) .add(); - vl2.getNodeBreakerView().newSwitch() - .setId("K") - .setKind(SwitchKind.DISCONNECTOR) - .setRetained(true) - .setOpen(false) - .setNode1(1) - .setNode2(3) + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS3") + .setNode(2) .add(); - vl2.getNodeBreakerView().newSwitch() - .setId("L") + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW3") .setKind(SwitchKind.BREAKER) .setRetained(true) - .setOpen(false) + .setOpen(true) .setNode1(2) .setNode2(3) .add(); - vl2.getNodeBreakerView().newSwitch() - .setId("M") + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS4") + .setNode(3) + .add(); + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW4") .setKind(SwitchKind.BREAKER) .setRetained(false) - .setOpen(false) - .setNode1(0) + .setOpen(true) + .setNode1(3) .setNode2(4) .add(); - vl2.getNodeBreakerView().newBusbarSection() - .setId("newBbs1") - .setNode(5) + vl1.getNodeBreakerView().newBusbarSection() + .setId("BBS5") + .setNode(4) .add(); - vl2.getNodeBreakerView().newSwitch() - .setId("newDisconnector1") - .setKind(SwitchKind.DISCONNECTOR) + vl1.getNodeBreakerView().newSwitch() + .setId("VL1SW5") + .setKind(SwitchKind.BREAKER) .setRetained(true) - .setOpen(false) - .setNode1(5) + .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(); - vl2.getNodeBreakerView().newSwitch() - .setId("newBreaker1") + 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(6) - .setNode2(7) + .setNode1(0) + .setNode2(11) .add(); - vl2.newGenerator() - .setId("newGen1") - .setNode(7) - .setMaxP(100.0) - .setMinP(50.0) - .setTargetP(100.0) - .setTargetV(400.0) - .setVoltageRegulatorOn(true) + vl1.newLoad().setId("VL1L6").setNode(11).setP0(1).setQ0(1).add().getTerminal().setP(0.001).setQ(1); + + 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(); - network.newLine() - .setId("N") - .setR(0.001) - .setX(0.1) - .setG1(0.0) - .setB1(0.0) - .setG2(0.0) - .setB2(0.0) - .setVoltageLevel1("B") - .setNode1(2) - .setVoltageLevel2("G") - .setNode2(4) + 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(); - // set 2 different V values for the 2 buses in the bus view of voltage level vl2 - assertNotNull(vl2.getBusView().getBus("G_0")); - vl2.getBusView().getBus("G_0").setV(230.); - assertNotNull(vl2.getBusView().getBus("G_5")); - vl2.getBusView().getBus("G_5").setV(250.); - - // 2 buses in the bus breaker view of voltage level vl2 have V value = 230 - assertNotNull(vl2.getBusBreakerView().getBus("G_0")); - assertEquals(230., vl2.getBusBreakerView().getBus("G_0").getV(), 0.0); - assertNotNull(vl2.getBusBreakerView().getBus("G_1")); - assertEquals(230., vl2.getBusBreakerView().getBus("G_1").getV(), 0.0); - - // the 4 other buses in the bus breaker view of voltage level vl2 have V value = 250 - assertNotNull(vl2.getBusBreakerView().getBus("G_2")); - assertEquals(250., vl2.getBusBreakerView().getBus("G_2").getV(), 0.0); - assertNotNull(vl2.getBusBreakerView().getBus("G_3")); - assertEquals(250., vl2.getBusBreakerView().getBus("G_3").getV(), 0.0); - assertNotNull(vl2.getBusBreakerView().getBus("G_5")); - assertEquals(250., vl2.getBusBreakerView().getBus("G_5").getV(), 0.0); - assertNotNull(vl2.getBusBreakerView().getBus("G_6")); - assertEquals(250., vl2.getBusBreakerView().getBus("G_6").getV(), 0.0); - - // set 2 different V values for 2 buses in the bus breaker view of voltage level vl2, which correspond to 2 - // different buses in the bus view - vl2.getBusBreakerView().getBus("G_1").setV(270.); - vl2.getBusBreakerView().getBus("G_5").setV(290.); - - assertEquals(270., vl2.getBusView().getBus("G_0").getV(), 0.0); - assertEquals(290., vl2.getBusView().getBus("G_5").getV(), 0.0); + // 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); + // BUS6 has no load it will be pruned + + return (VoltageLevelImpl) vl2; + } + + private void testSetVMultipleBusAcrossViews(VoltageLevelImpl vl, Map busBreakerViewBusToBusViewBus, Map> busViewBusToBusBreakerViewBus) { + for (Map.Entry entry : busBreakerViewBusToBusViewBus.entrySet()) { + String busbreakerviewbusid = entry.getKey(); + String busviewbusid = entry.getValue(); + + // should we replace with new network for each test ?? + vl.invalidateCalculatedBuses(); // but this keeps previous v values + // TODO this forces a computation of all busviews + // need to test when views are not initialized + 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())); + } + } + } + + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + + // should we replace with new network for each test ?? + vl.invalidateCalculatedBuses(); // but this keeps previous v values + // TODO this forces a computation of all busviews + // need to test when views are not initialized + 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()))); } } From d09854e7359b6f6cde6c49fc6ccd8c35e4ebcc60 Mon Sep 17 00:00:00 2001 From: HARPER Jon Date: Fri, 27 Sep 2024 18:13:47 +0200 Subject: [PATCH 05/15] wip fix syncv Signed-off-by: HARPER Jon --- .../store/iidm/impl/AbstractTopology.java | 4 ++-- .../store/iidm/impl/CalculatedBus.java | 13 ++++++------- .../store/iidm/impl/ConfiguredBusImpl.java | 19 +++++++++---------- 3 files changed, 17 insertions(+), 19 deletions(-) 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 cc1e0f5e3..547aad9eb 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 @@ -423,11 +423,11 @@ private CalculationResult getCalculatedBusAttributesList(NetworkObjectIndex i nodeOrBusToCalculatedBusNum = getNodeOrBusToCalculatedBusNum(voltageLevelResource, isBusView); } else { List> connectedSetList = findConnectedSetList(index, voltageLevelResource, isBusView); - AtomicDouble v = new AtomicDouble(Double.NaN); - AtomicDouble angle = new AtomicDouble(Double.NaN); 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()); 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 4ff2e8d3f..eb8f851f5 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 @@ -200,13 +200,12 @@ private void updateConfiguredBuses(double newValue, String attributeName, ToDoubleFunction getValue, ObjDoubleConsumer setValue) { - List busesIds = calculatedBusAttributes.getVertices().stream() - .map(Vertex::getBus) - .toList(); - - List buses = getVoltageLevel().getBusBreakerView().getBusStream() - .filter(bus -> busesIds.contains(bus.getId()) && !Objects.equals(getValue.applyAsDouble(bus), newValue)) - .toList(); + 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( 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 c2c75eda5..01ee794b4 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 @@ -29,9 +29,9 @@ import com.powsybl.network.store.model.CalculatedBusAttributes; import com.powsybl.network.store.model.ConfiguredBusAttributes; import com.powsybl.network.store.model.Resource; -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; @@ -99,15 +99,14 @@ private void updateCalculatedBusAttributes(double newValue, .getAttributes() .getCalculatedBusesForBusView(); - if (CollectionUtils.isNotEmpty(calculatedBusAttributesList)) { - getAllTerminals().stream() - .filter(Terminal::isConnected) - .map(t -> ((CalculatedBus) t.getBusView().getBus()).getCalculatedBusNum()) - .findFirst() - .ifPresent(calculatedBusNum -> { - setValue.accept(calculatedBusAttributesList.get(calculatedBusNum), newValue); - index.updateVoltageLevelResource(voltageLevel.getResource(), AttributeFilter.SV); - }); + Map listCalculatedBuses = voltageLevel.getResource().getAttributes().getBusToCalculatedBusForBusView(); + if (listCalculatedBuses != null) { + Integer busviewnum = listCalculatedBuses.get(getId()); + if (busviewnum != null) { + CalculatedBusAttributes busviewattributes = voltageLevel.getResource().getAttributes().getCalculatedBusesForBusView().get(busviewnum); + setValue.accept(busviewattributes, newValue); + index.updateVoltageLevelResource(voltageLevel.getResource(), AttributeFilter.SV); + } } }); } From ed428368dc49014cd39a02be6c5189884dc4eee1 Mon Sep 17 00:00:00 2001 From: HARPER Jon Date: Mon, 30 Sep 2024 12:44:43 +0200 Subject: [PATCH 06/15] more fixes using busnum instead of vertex Signed-off-by: HARPER Jon --- .../store/iidm/impl/AbstractTopology.java | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) 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 547aad9eb..b14889677 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 @@ -393,21 +393,42 @@ private void getVAndAngleFromOtherView(NetworkObjectIndex index, AtomicDouble angle, boolean isBusView) { AtomicBoolean foundInCalculatedBuses = new AtomicBoolean(false); + + //TODO CLEANUP List calculatedBusAttributes = isBusView ? voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView() : voltageLevelResource.getAttributes().getCalculatedBusesForBusView(); + if (!CollectionUtils.isEmpty(calculatedBusAttributes)) { - connectedSet.getConnectedVertices().forEach(vertex -> - calculatedBusAttributes.stream().filter(attr -> attr.getVertices().contains(vertex)).forEach(b -> { - if (!Double.isNaN(b.getV())) { - v.set(b.getV()); - foundInCalculatedBuses.set(true); - } - if (!Double.isNaN(b.getAngle())) { - angle.set(b.getAngle()); - foundInCalculatedBuses.set(true); - } - })); + Integer busnum = null; + if (voltageLevelResource.getAttributes().getTopologyKind() == TopologyKind.NODE_BREAKER) { + Map nums = isBusView ? + voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusBreakerView() : + voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusView(); + if (nums != null) { + busnum = nums.get(connectedSet.getConnectedNodesOrBuses().iterator().next()); + } + } else { + Map nums = isBusView ? + voltageLevelResource.getAttributes().getBusToCalculatedBusForBusBreakerView() : + voltageLevelResource.getAttributes().getBusToCalculatedBusForBusView(); + if (nums != null) { + busnum = nums.get(connectedSet.getConnectedNodesOrBuses().iterator().next()); + } + } + CalculatedBusAttributes busattributes = null; + if (busnum != null) { + busattributes = calculatedBusAttributes.get(busnum); + } + + if (busattributes != null && !Double.isNaN(busattributes.getV())) { + v.set(busattributes.getV()); + foundInCalculatedBuses.set(true); + } + if (busattributes != null && !Double.isNaN(busattributes.getAngle())) { + angle.set(busattributes.getAngle()); + foundInCalculatedBuses.set(true); + } } if (isBusView && !foundInCalculatedBuses.get()) { // get V and Angle values from configured buses From 54ef522066259c5ac0ad665f9480a4078edbac98 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Mon, 30 Sep 2024 17:32:33 +0200 Subject: [PATCH 07/15] Fix updateBusesAttributes method in CalculatedBus.java Signed-off-by: Franck LECUYER --- .../store/iidm/impl/CalculatedBus.java | 61 ++++++++++--------- .../store/iidm/impl/ConfiguredBusImpl.java | 15 ++--- 2 files changed, 38 insertions(+), 38 deletions(-) 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 eb8f851f5..7fa8c7e56 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 @@ -16,6 +16,7 @@ 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.function.Function; @@ -168,35 +169,44 @@ public Bus setV(double v) { if (getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) { // update V in configured buses - updateConfiguredBuses(v, getAttributes(), VOLTAGE, this::getVInBus, this::setVInConfiguredBus); + updateConfiguredBuses(v, VOLTAGE, this::getVInBus, this::setVInConfiguredBus); } else { - if (isBusView) { - // update V for buses in BusBreakerView - updateBusesAttributes(v, voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView(), getAttributes(), this::setVInCalculatedBus); - } else { - // update V for buses in BusView - updateBusesAttributes(v, voltageLevelResource.getAttributes().getCalculatedBusesForBusView(), getAttributes(), this::setVInCalculatedBus); - } + // update V for buses in the other view (busView/busBreakerView) + updateBusesAttributes(v, this::setVInCalculatedBus); } return this; } - private void updateBusesAttributes(double value, - List calculatedBusAttributesList, - CalculatedBusAttributes sourceBusAttributes, - ObjDoubleConsumer setValue) { - if (!CollectionUtils.isEmpty(calculatedBusAttributesList)) { - calculatedBusAttributesList.forEach(busToUpdate -> busToUpdate.getVertices().forEach(vertex1 -> - sourceBusAttributes.getVertices().stream().filter(v -> v.getId().equals(vertex1.getId())).findFirst().ifPresent(vertex2 -> { - setValue.accept(busToUpdate, value); - index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV); - }) - )); + 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)) { + 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, - CalculatedBusAttributes calculatedBusAttributes, String attributeName, ToDoubleFunction getValue, ObjDoubleConsumer setValue) { @@ -236,15 +246,10 @@ public Bus setAngle(double angle) { if (getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) { // update angle in configuredBus - updateConfiguredBuses(angle, getAttributes(), ANGLE, this::getAngleInBus, this::setAngleInConfiguredBus); + updateConfiguredBuses(angle, ANGLE, this::getAngleInBus, this::setAngleInConfiguredBus); } else { - if (isBusView) { - // update angle for Bus in BusBreakerView - updateBusesAttributes(angle, voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView(), getAttributes(), this::setAngleInCalculatedBus); - } else { - // update angle for Bus in BusView - updateBusesAttributes(angle, voltageLevelResource.getAttributes().getCalculatedBusesForBusView(), getAttributes(), this::setAngleInCalculatedBus); - } + // update angle for buses in the other view (busView/busBreakerView) + updateBusesAttributes(angle, this::setAngleInCalculatedBus); } return this; } 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 01ee794b4..2c5b1a971 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 @@ -29,6 +29,7 @@ import com.powsybl.network.store.model.CalculatedBusAttributes; import com.powsybl.network.store.model.ConfiguredBusAttributes; import com.powsybl.network.store.model.Resource; +import org.apache.commons.collections4.MapUtils; import java.util.List; import java.util.Map; @@ -92,16 +93,10 @@ private void setAngleInCalculatedBus(CalculatedBusAttributes calculatedBusAttrib private void updateCalculatedBusAttributes(double newValue, String voltageLevelId, ObjDoubleConsumer setValue) { - Optional voltageLevelOpt = index.getVoltageLevel(voltageLevelId); - - voltageLevelOpt.ifPresent(voltageLevel -> { - List calculatedBusAttributesList = voltageLevel.getResource() - .getAttributes() - .getCalculatedBusesForBusView(); - - Map listCalculatedBuses = voltageLevel.getResource().getAttributes().getBusToCalculatedBusForBusView(); - if (listCalculatedBuses != null) { - Integer busviewnum = listCalculatedBuses.get(getId()); + 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); From d59aadac5ede5e023e7842325049b5a70620df20 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Wed, 2 Oct 2024 15:29:18 +0200 Subject: [PATCH 08/15] Cleaning and optimizing code Signed-off-by: Franck LECUYER --- .../store/iidm/impl/AbstractTopology.java | 81 +++++++++---------- .../store/iidm/impl/CalculatedBus.java | 19 +++-- 2 files changed, 45 insertions(+), 55 deletions(-) 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 b14889677..b5282e7dc 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 @@ -11,12 +11,12 @@ 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; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -367,23 +367,25 @@ private void getVAndAngleFromConfiguredBus(NetworkObjectIndex index, ConnectedSetResult connectedSet, AtomicDouble v, AtomicDouble angle) { + AtomicReference foundConfiguredBus = new AtomicReference<>(); index.getConfiguredBuses(voltageLevelResource.getId()).forEach(bus -> { - ConfiguredBusImpl configuredBus = (ConfiguredBusImpl) bus; - AtomicReference foundConfiguredBus = new AtomicReference<>(); - 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()); + 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, @@ -392,45 +394,34 @@ private void getVAndAngleFromOtherView(NetworkObjectIndex index, AtomicDouble v, AtomicDouble angle, boolean isBusView) { - AtomicBoolean foundInCalculatedBuses = new AtomicBoolean(false); - - //TODO CLEANUP - List calculatedBusAttributes = isBusView ? - voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView() : - voltageLevelResource.getAttributes().getCalculatedBusesForBusView(); + boolean foundInCalculatedBuses = false; + List calculatedBusAttributes = isBusView ? voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView() : voltageLevelResource.getAttributes().getCalculatedBusesForBusView(); if (!CollectionUtils.isEmpty(calculatedBusAttributes)) { - Integer busnum = null; + Integer busNum = null; if (voltageLevelResource.getAttributes().getTopologyKind() == TopologyKind.NODE_BREAKER) { - Map nums = isBusView ? - voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusBreakerView() : - voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusView(); - if (nums != null) { - busnum = nums.get(connectedSet.getConnectedNodesOrBuses().iterator().next()); + Map nodesToCalculatedBuses = isBusView ? voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusBreakerView() : voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusView(); + if (!MapUtils.isEmpty(nodesToCalculatedBuses)) { + Integer node = (Integer) connectedSet.getConnectedNodesOrBuses().iterator().next(); + busNum = nodesToCalculatedBuses.get(node); } } else { - Map nums = isBusView ? - voltageLevelResource.getAttributes().getBusToCalculatedBusForBusBreakerView() : - voltageLevelResource.getAttributes().getBusToCalculatedBusForBusView(); - if (nums != null) { - busnum = nums.get(connectedSet.getConnectedNodesOrBuses().iterator().next()); + Map busesToCalculatedBuses = isBusView ? voltageLevelResource.getAttributes().getBusToCalculatedBusForBusBreakerView() : voltageLevelResource.getAttributes().getBusToCalculatedBusForBusView(); + if (!MapUtils.isEmpty(busesToCalculatedBuses)) { + String bus = (String) connectedSet.getConnectedNodesOrBuses().iterator().next(); + busNum = busesToCalculatedBuses.get(bus); } } - CalculatedBusAttributes busattributes = null; - if (busnum != null) { - busattributes = calculatedBusAttributes.get(busnum); - } - - if (busattributes != null && !Double.isNaN(busattributes.getV())) { - v.set(busattributes.getV()); - foundInCalculatedBuses.set(true); - } - if (busattributes != null && !Double.isNaN(busattributes.getAngle())) { - angle.set(busattributes.getAngle()); - foundInCalculatedBuses.set(true); + if (busNum != null) { + CalculatedBusAttributes busAttributes = calculatedBusAttributes.get(busNum); + if (busAttributes != null) { + v.set(busAttributes.getV()); + angle.set(busAttributes.getAngle()); + foundInCalculatedBuses = true; + } } } - if (isBusView && !foundInCalculatedBuses.get()) { + if (isBusView && !foundInCalculatedBuses) { // get V and Angle values from configured buses getVAndAngleFromConfiguredBus(index, voltageLevelResource, connectedSet, v, angle); } 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 7fa8c7e56..5f96222ba 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 @@ -190,16 +190,15 @@ private void updateBusesAttributes(double value, ObjDoubleConsumer nodesToCalculatedBusesInOtherView = isBusView ? vlAttributes.getNodeToCalculatedBusForBusBreakerView() : vlAttributes.getNodeToCalculatedBusForBusView(); - if (!MapUtils.isEmpty(nodesToCalculatedBusesInOtherView)) { - 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); - } + 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); } } }); From ebe387e58b6e5f7d5dd2d2de843ff53328b17209 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Thu, 3 Oct 2024 13:30:06 +0200 Subject: [PATCH 09/15] Move methods in another file Signed-off-by: Franck LECUYER --- .../store/iidm/impl/VoltageLevelSetVTest.java | 305 ++++++++++++++++++ .../store/iidm/impl/VoltageLevelTest.java | 282 ---------------- 2 files changed, 305 insertions(+), 282 deletions(-) create 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/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 new file mode 100644 index 000000000..d62248264 --- /dev/null +++ b/network-store-iidm-impl/src/test/java/com/powsybl/network/store/iidm/impl/VoltageLevelSetVTest.java @@ -0,0 +1,305 @@ +/** + * 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.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Slimane Amar + */ +public class VoltageLevelSetVTest { + @Test + public void testWithMultipleBusInBusBreakerAndBusView() { + Network network = Network.create("test_mcc", "test"); + + VoltageLevelImpl vl1 = createNodeBreaker(network); + VoltageLevelImpl vl2 = createBusBreaker(network); + + testSetVMultipleBusAcrossViews(vl1, NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS, NBVL_BUSVIEWBUS_TO_BUSBREAKERVIEWBUS); + testSetVMultipleBusAcrossViews(vl2, 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", "" + )); + 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); + + 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(VoltageLevelImpl vl, Map busBreakerViewBusToBusViewBus, Map> busViewBusToBusBreakerViewBus) { + for (Map.Entry entry : busBreakerViewBusToBusViewBus.entrySet()) { + String busbreakerviewbusid = entry.getKey(); + String busviewbusid = entry.getValue(); + + // should we replace with new network for each test ?? + vl.invalidateCalculatedBuses(); // but this keeps previous v values + // TODO this forces a computation of all busviews + // need to test when views are not initialized + 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())); + } + } + } + + for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { + String busviewbusid = entry.getKey(); + List busbreakerviewbusids = entry.getValue(); + + // should we replace with new network for each test ?? + vl.invalidateCalculatedBuses(); // but this keeps previous v values + // TODO this forces a computation of all busviews + // need to test when views are not initialized + 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 33df20173..28917edc5 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 @@ -9,8 +9,6 @@ import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.extensions.BusbarSectionPositionAdder; import org.junit.Test; -import java.util.*; -import java.util.stream.*; import static org.junit.Assert.*; @@ -158,284 +156,4 @@ public void testBusbarSectionPositions() { assertEquals("Busbar section 'idBBS': Busbar index has to be greater or equals to zero", assertThrows(ValidationException.class, busbarSectionPositionAdder::add).getMessage()); } - - @Test - //TODO move all this to a separate file it's huge. - public void testWithMultipleBusInBusBreakerAndBusView() { - Network network = Network.create("test_mcc", "test"); - - VoltageLevelImpl vl1 = createNodeBreaker(network); - VoltageLevelImpl vl2 = createBusBreaker(network); - - testSetVMultipleBusAcrossViews(vl1, NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS, NBVL_BUSVIEWBUS_TO_BUSBREAKERVIEWBUS); - testSetVMultipleBusAcrossViews(vl2, 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", "" - )); - 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); - - 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); - // BUS6 has no load it will be pruned - - return (VoltageLevelImpl) vl2; - } - - private void testSetVMultipleBusAcrossViews(VoltageLevelImpl vl, Map busBreakerViewBusToBusViewBus, Map> busViewBusToBusBreakerViewBus) { - for (Map.Entry entry : busBreakerViewBusToBusViewBus.entrySet()) { - String busbreakerviewbusid = entry.getKey(); - String busviewbusid = entry.getValue(); - - // should we replace with new network for each test ?? - vl.invalidateCalculatedBuses(); // but this keeps previous v values - // TODO this forces a computation of all busviews - // need to test when views are not initialized - 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())); - } - } - } - - for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { - String busviewbusid = entry.getKey(); - List busbreakerviewbusids = entry.getValue(); - - // should we replace with new network for each test ?? - vl.invalidateCalculatedBuses(); // but this keeps previous v values - // TODO this forces a computation of all busviews - // need to test when views are not initialized - 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()))); - } } From 16edded0c718aa1935bbc829e09e9ab92e3a913b Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Thu, 3 Oct 2024 15:46:36 +0200 Subject: [PATCH 10/15] Add configuration similar to the one in AbstractMainConnectedComponentWithSwitchTest, with buses in bus breaker view between retained switches and with no equipments. Signed-off-by: Franck LECUYER --- .../store/iidm/impl/VoltageLevelSetVTest.java | 65 ++++++++++++++----- 1 file changed, 48 insertions(+), 17 deletions(-) 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 index d62248264..be0b81c06 100644 --- 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 @@ -16,6 +16,7 @@ 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; @@ -27,13 +28,14 @@ public class VoltageLevelSetVTest { @Test public void testWithMultipleBusInBusBreakerAndBusView() { - Network network = Network.create("test_mcc", "test"); - - VoltageLevelImpl vl1 = createNodeBreaker(network); - VoltageLevelImpl vl2 = createBusBreaker(network); - - testSetVMultipleBusAcrossViews(vl1, NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS, NBVL_BUSVIEWBUS_TO_BUSBREAKERVIEWBUS); - testSetVMultipleBusAcrossViews(vl2, BBVL_CONFIGUREDBUS_TO_BUSVIEWBUS, BBVL_BUSVIEWBUS_TO_CONFIGUREDBUS); + 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( @@ -43,7 +45,10 @@ public void testWithMultipleBusInBusBreakerAndBusView() { "VL1_4", "VL1_4", "VL1_10", "", "VL1_11", "VL1_0", - "VL1_5", "" + "VL1_5", "", + "VL1_12", "", + "VL1_13", "", + "VL1_14", "" )); private static final Map> NBVL_BUSVIEWBUS_TO_BUSBREAKERVIEWBUS = invertMap(NBVL_BUSBREAKERVIEWBUS_TO_BUSVIEWBUS); @@ -168,6 +173,37 @@ private static VoltageLevelImpl createNodeBreaker(Network network) { .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; } @@ -226,15 +262,13 @@ private static VoltageLevelImpl createBusBreaker(Network network) { return (VoltageLevelImpl) vl2; } - private void testSetVMultipleBusAcrossViews(VoltageLevelImpl vl, Map busBreakerViewBusToBusViewBus, Map> busViewBusToBusBreakerViewBus) { + 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(); - // should we replace with new network for each test ?? - vl.invalidateCalculatedBuses(); // but this keeps previous v values - // TODO this forces a computation of all busviews - // need to test when views are not initialized for (Bus bbvb : vl.getBusBreakerView().getBuses()) { bbvb.setV(Double.NaN); } @@ -261,14 +295,11 @@ private void testSetVMultipleBusAcrossViews(VoltageLevelImpl vl, Map > entry : busViewBusToBusBreakerViewBus.entrySet()) { String busviewbusid = entry.getKey(); List busbreakerviewbusids = entry.getValue(); - // should we replace with new network for each test ?? - vl.invalidateCalculatedBuses(); // but this keeps previous v values - // TODO this forces a computation of all busviews - // need to test when views are not initialized for (Bus bbvb : vl.getBusBreakerView().getBuses()) { bbvb.setV(Double.NaN); } From 15df0fa6141e71524f39d7cda7e44d3d77dfcac4 Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Fri, 4 Oct 2024 09:18:26 +0200 Subject: [PATCH 11/15] Clean code in getVAndAngleFromOtherView method Signed-off-by: Franck LECUYER --- .../store/iidm/impl/AbstractTopology.java | 35 +++++++------------ 1 file changed, 12 insertions(+), 23 deletions(-) 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 b5282e7dc..c7e089d1d 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 @@ -394,34 +394,23 @@ private void getVAndAngleFromOtherView(NetworkObjectIndex index, AtomicDouble v, AtomicDouble angle, boolean isBusView) { - boolean foundInCalculatedBuses = false; - - List calculatedBusAttributes = isBusView ? voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView() : voltageLevelResource.getAttributes().getCalculatedBusesForBusView(); - if (!CollectionUtils.isEmpty(calculatedBusAttributes)) { - Integer busNum = null; - if (voltageLevelResource.getAttributes().getTopologyKind() == TopologyKind.NODE_BREAKER) { + if (voltageLevelResource.getAttributes().getTopologyKind() == TopologyKind.NODE_BREAKER) { + 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(); - busNum = nodesToCalculatedBuses.get(node); - } - } else { - Map busesToCalculatedBuses = isBusView ? voltageLevelResource.getAttributes().getBusToCalculatedBusForBusBreakerView() : voltageLevelResource.getAttributes().getBusToCalculatedBusForBusView(); - if (!MapUtils.isEmpty(busesToCalculatedBuses)) { - String bus = (String) connectedSet.getConnectedNodesOrBuses().iterator().next(); - busNum = busesToCalculatedBuses.get(bus); - } - } - if (busNum != null) { - CalculatedBusAttributes busAttributes = calculatedBusAttributes.get(busNum); - if (busAttributes != null) { - v.set(busAttributes.getV()); - angle.set(busAttributes.getAngle()); - foundInCalculatedBuses = true; + Integer busNum = nodesToCalculatedBuses.get(node); + if (busNum != null) { + CalculatedBusAttributes busAttributes = calculatedBusAttributes.get(busNum); + if (busAttributes != null) { + v.set(busAttributes.getV()); + angle.set(busAttributes.getAngle()); + } + } } } - } - if (isBusView && !foundInCalculatedBuses) { + } else { // get V and Angle values from configured buses getVAndAngleFromConfiguredBus(index, voltageLevelResource, connectedSet, v, angle); } From 68535165f5103c655035874e4111c44d44ac90b8 Mon Sep 17 00:00:00 2001 From: HARPER Jon Date: Tue, 22 Oct 2024 18:32:26 +0200 Subject: [PATCH 12/15] VoltageLevelTest, Add more combinations to setV/setAngle for cache existence Signed-off-by: HARPER Jon --- .../store/iidm/impl/VoltageLevelTest.java | 60 ++++++++++++------- 1 file changed, 39 insertions(+), 21 deletions(-) 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 28917edc5..673191a89 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 @@ -18,72 +18,90 @@ 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 + // Update voltage using BusView calculated bus, should also set the BusBreakerView configured bus immediately l1.getTerminal1().getBusView().getBus().setV(222); - // Verify the voltage update in BusBreakerView + // 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 + // Set voltage using BusBreakerView configured bus, should set the existing cache of the BusView calculated bus immediately l1.getTerminal1().getBusBreakerView().getBus().setV(400.0); - // Verify voltage update in BusView + // 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 + // Update voltage using BusBreakerView calculated bus, with no cache for BusView calculated bus so no immediate update. l1.getTerminal1().getBusBreakerView().getBus().setV(222); - // Verify the voltage update in BusView + // 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 + // 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 BusBreakerView + // 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 BusView - l1.getTerminal1().getBusView().getBus().setAngle(111); + // Update angle using BusBreakerView configured bus, with no cache for BusView calculated bus so no immediate update. + l1.getTerminal1().getBusBreakerView().getBus().setAngle(111); - // Verify the angle update in BusBreakerView - assertEquals("Angle should match in BusView after update in BusBreakerView", 111, l1.getTerminal1().getBusBreakerView().getBus().getAngle(), 0.0); + // 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 BusBreakerView - l1.getTerminal1().getBusBreakerView().getBus().setAngle(400.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 BusView + // 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 BusBreakerView - l1.getTerminal1().getBusBreakerView().getBus().setAngle(222); + // 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 BusView - assertEquals("Angle should match in BusBreakerView after second update in BusView", 222, l1.getTerminal1().getBusView().getBus().getAngle(), 0.0); + // 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 BusView - l1.getTerminal1().getBusView().getBus().setAngle(400.0); + // Set angle using BusBreakerView calculated bus, should also setAngle in the existing BusView calculated bus cache immediately + 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); From 9c17fe0283f131fd41d0e2e178a45151e4c9e6db Mon Sep 17 00:00:00 2001 From: HARPER Jon Date: Wed, 4 Dec 2024 16:57:35 +0100 Subject: [PATCH 13/15] 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 From e9f18ade5257a51e700130ae73d9128d3623f050 Mon Sep 17 00:00:00 2001 From: HARPER Jon Date: Thu, 26 Dec 2024 17:12:34 +0100 Subject: [PATCH 14/15] add more checks for isCalculatedBusValid for updates and code improvements from reviews Signed-off-by: HARPER Jon --- .../store/iidm/impl/AbstractTopology.java | 54 +++++++++++-------- .../store/iidm/impl/CalculatedBus.java | 49 +++++++++++------ .../store/iidm/impl/ConfiguredBusImpl.java | 29 +++++----- ...geLevelSetVAngleInCalculatedViewsTest.java | 26 ++++----- 4 files changed, 96 insertions(+), 62 deletions(-) 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 e6df13b73..2e56ce2d4 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 @@ -362,21 +362,26 @@ private void setCalculatedBuses(Resource voltageLevelRes 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(); // non deterministic - Integer busNum = nodesToCalculatedBuses.get(node); - if (busNum != null) { - return calculatedBusAttributes.get(busNum); - } - } + // 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 = (Integer) nodes.iterator().next(); + Integer busNumInOtherView = nodesToCalculatedBusesInOtherView.get(node); + if (busNumInOtherView != null) { + return calculatedBusAttributesInOtherView.get(busNumInOtherView); } } return null; @@ -395,13 +400,18 @@ private CalculatedBusAttributes createCalculatedBusAttributesWithVAndAngle(Netwo 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(); + // 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); } 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 32fc88dc0..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 @@ -511,7 +511,15 @@ public int getCalculatedBusNum() { } 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 + // 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() @@ -519,19 +527,23 @@ private void updateBusesAttributes(double value, ObjDoubleConsumer nodesToCalculatedBusesInOtherView = isBusView ? vlAttributes.getNodeToCalculatedBusForBusBreakerView() : vlAttributes.getNodeToCalculatedBusForBusView(); - if (!MapUtils.isEmpty(nodesToCalculatedBuses) && !MapUtils.isEmpty(nodesToCalculatedBusesInOtherView)) { + 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(); - 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); - } + 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; } } } @@ -540,11 +552,18 @@ private void updateBusesAttributes(double value, 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(); - for (Entry entry : vlAttributes.getBusToCalculatedBusForBusView().entrySet()) { - if (getCalculatedBusNum() == entry.getValue()) { - Bus bus = getVoltageLevel().getBusBreakerView().getBus(entry.getKey()); - setValue.accept((ConfiguredBusImpl) bus, newValue); + 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 4f75ebe21..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 @@ -28,8 +28,10 @@ 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; @@ -96,8 +98,8 @@ private void setV(double v, boolean updateCalculatedBus) { } } - // update without the part setting values in calculated buses otherwise - // it leads to infinite loops because calculated buses also update configured buses + // 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); } @@ -148,23 +150,26 @@ private void setAngleInCalculatedBus(CalculatedBusAttributes calculatedBusAttrib 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)) { + 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 busviewattributes = voltageLevel.getResource().getAttributes().getCalculatedBusesForBusView().get(busviewnum); + 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(busviewattributes, newValue); + setValue.accept(busViewCalculatedBusAttributes, newValue); index.updateVoltageLevelResource(voltageLevel.getResource(), AttributeFilter.SV); } } 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 index 3b6809963..7b1e7ca29 100644 --- 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 @@ -364,7 +364,7 @@ private void testSetMultipleBusAcrossViews(Supplier networkVoltage for (Map.Entry entry : busBreakerViewBusToBusViewBus.entrySet()) { String busbreakerviewbusid = entry.getKey(); String busviewbusid = entry.getValue(); - setAllBbvbAndBvb(vl, Double.NaN, setter); + setAllBusBreakerViewBusAndBusViewBus(vl, Double.NaN, setter); setAndCheckBusBreakerBus(vl, busbreakerviewbusid, busviewbusid, getter, setter); } @@ -429,7 +429,7 @@ private void testSetMultipleBusAcrossViews(Supplier networkVoltage if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) { // test one same view vl = networkVoltageLevelSupplier.get(); - setAllBbvb(vl, 1.0, setter); + 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", @@ -438,7 +438,7 @@ private void testSetMultipleBusAcrossViews(Supplier networkVoltage // test one other view vl = networkVoltageLevelSupplier.get(); - setAllBvb(vl, 1.0, setter); + 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", @@ -450,7 +450,7 @@ private void testSetMultipleBusAcrossViews(Supplier networkVoltage // so test only with busview view // NOTE: for busbreakertopology we keep the previous values. vl = networkVoltageLevelSupplier.get(); - setAllBvb(vl, 1.0, setter); + setAllBusViewBus(vl, 1.0, setter); ((VoltageLevelImpl) vl).invalidateCalculatedBuses(); for (Bus bbvb : vl.getBusBreakerView().getBuses()) { if (!busBreakerViewBusToBusViewBus.get(bbvb.getId()).isEmpty()) { @@ -486,7 +486,7 @@ private void testSetMultipleBusAcrossViews(Supplier networkVoltage for (Map.Entry > entry : busViewBusToBusBreakerViewBus.entrySet()) { String busviewbusid = entry.getKey(); List busbreakerviewbusids = entry.getValue(); - setAllBbvbAndBvb(vl, Double.NaN, setter); + setAllBusBreakerViewBusAndBusViewBus(vl, Double.NaN, setter); setAndCheckBusViewBus(vl, busviewbusid, busbreakerviewbusids, getter, setter); } @@ -546,7 +546,7 @@ private void testSetMultipleBusAcrossViews(Supplier networkVoltage // 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); + 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", @@ -555,7 +555,7 @@ private void testSetMultipleBusAcrossViews(Supplier networkVoltage // test one other view vl = networkVoltageLevelSupplier.get(); - setAllBvb(vl, 1.0, setter); + 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", @@ -567,7 +567,7 @@ private void testSetMultipleBusAcrossViews(Supplier networkVoltage // so test only with busview view // NOTE: for busbreakertopology we keep the previous values. vl = networkVoltageLevelSupplier.get(); - setAllBvb(vl, 1.0, setter); + 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", @@ -576,20 +576,20 @@ private void testSetMultipleBusAcrossViews(Supplier networkVoltage } } - private void setAllBbvbAndBvb(VoltageLevel vl, double value, BiConsumer setter) { + 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. - setAllBbvb(vl, value, setter); - setAllBvb(vl, value, setter); + setAllBusBreakerViewBus(vl, value, setter); + setAllBusViewBus(vl, value, setter); } - private void setAllBvb(VoltageLevel vl, double value, BiConsumer setter) { + private void setAllBusViewBus(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) { + private void setAllBusBreakerViewBus(VoltageLevel vl, double value, BiConsumer setter) { for (Bus bbvb : vl.getBusBreakerView().getBuses()) { setter.accept(bbvb, value); } From 02033a49e05bfd92fc3de2fc103dd587291aa811 Mon Sep 17 00:00:00 2001 From: HARPER Jon Date: Thu, 26 Dec 2024 22:38:49 +0100 Subject: [PATCH 15/15] remove useless cast as per sonar Signed-off-by: HARPER Jon --- .../com/powsybl/network/store/iidm/impl/AbstractTopology.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2e56ce2d4..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 @@ -378,7 +378,7 @@ private CalculatedBusAttributes findFirstMatchingNodeBreakerCalculatedBusAttribu // 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 = (Integer) nodes.iterator().next(); + Integer node = nodes.iterator().next(); Integer busNumInOtherView = nodesToCalculatedBusesInOtherView.get(node); if (busNumInOtherView != null) { return calculatedBusAttributesInOtherView.get(busNumInOtherView);