From a43f0021e8f3a178e4bdb071df7de56ebcf8906c Mon Sep 17 00:00:00 2001 From: Franck LECUYER Date: Fri, 13 Sep 2024 12:38:16 +0200 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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()))); } }