From a984abb5a803cac1b571a2cc83418d4714e6c932 Mon Sep 17 00:00:00 2001 From: HARPER Jon Date: Thu, 5 Dec 2024 00:11:03 +0100 Subject: [PATCH] looks good --- .../store/iidm/impl/AbstractTopology.java | 92 ++++++++----------- .../store/iidm/impl/CalculatedBus.java | 82 ++++++----------- .../store/iidm/impl/ConfiguredBusImpl.java | 75 ++++++++++----- .../store/iidm/impl/VoltageLevelTest.java | 7 +- 4 files changed, 124 insertions(+), 132 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 7d1fc553a..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,63 +360,50 @@ private void setCalculatedBuses(Resource voltageLevelRes } } - private void getVAndAngleFromConfiguredBus(NetworkObjectIndex index, - Resource voltageLevelResource, - ConnectedSetResult connectedSet, - AtomicDouble v, - AtomicDouble angle) { - // Search with early return for a first configured bus with a connected terminal matching a vertex in the connectedSet of this bus - for (Bus bus: index.getConfiguredBuses(voltageLevelResource.getId())) { - ConfiguredBusImpl configuredBus = (ConfiguredBusImpl) bus; - for (Terminal t: configuredBus.getAllTerminals()) { - if (t.isConnected()) { - for (Vertex vertex: connectedSet.getConnectedVertices()) { - if (vertex.getId().equals(t.getConnectable().getId())) { - v.set(configuredBus.getResource().getAttributes().getV()); - angle.set(configuredBus.getResource().getAttributes().getAngle()); - return; - } + 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); } } } } + return null; } - private void getVAndAngleFromOtherView(NetworkObjectIndex index, + private CalculatedBusAttributes createCalculatedBusAttributesWithVAndAngle(NetworkObjectIndex index, Resource voltageLevelResource, ConnectedSetResult connectedSet, - AtomicDouble v, - AtomicDouble angle, boolean isBusView) { + double v = Double.NaN; + double angle = Double.NaN; if (voltageLevelResource.getAttributes().getTopologyKind() == TopologyKind.NODE_BREAKER) { - if (!voltageLevelResource.getAttributes().isCalculatedBusesValid()) { - // 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) - return; - } - 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 busNum = nodesToCalculatedBuses.get(node); - if (busNum != null) { - CalculatedBusAttributes busAttributes = calculatedBusAttributes.get(busNum); - if (busAttributes != null) { - v.set(busAttributes.getV()); - angle.set(busAttributes.getAngle()); - } - } - } + CalculatedBusAttributes busAttributes = findFirstMatchingNodeBreakerCalculatedBusAttributes(voltageLevelResource, connectedSet, isBusView); + if (busAttributes != null) { + v = busAttributes.getV(); + angle = busAttributes.getAngle(); } - } else { + } 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 - getVAndAngleFromConfiguredBus(index, voltageLevelResource, connectedSet, v, angle); + 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) { @@ -432,18 +417,13 @@ private CalculationResult getCalculatedBusAttributesList(NetworkObjectIndex i List> connectedSetList = findConnectedSetList(index, voltageLevelResource, isBusView); calculatedBusAttributesList = connectedSetList .stream() - .map(connectedSet -> { - AtomicDouble v = new AtomicDouble(Double.NaN); - AtomicDouble angle = new AtomicDouble(Double.NaN); - //TODO in this case in nodebreaker topology we currently don't preserve any values from - //the same view if it was already computed but is invalidated. - //we could do it some day (we need to define good heuristics to - //match previous values to new buses). - //NOTE: We chose to have the same behavior when getting the values from the other view - // get V and Angle values from other view if available - 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 b66e4d6a0..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); @@ -189,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); @@ -520,20 +511,19 @@ 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 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)) { + 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() @@ -543,35 +533,19 @@ private void updateBusesAttributes(double value, ObjDoubleConsumer 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()) - ); + 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 26456ce40..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,34 +82,58 @@ public double getAngle() { return getResource().getAttributes().getAngle(); } - @Override - public Bus setV(double v) { - if (v < 0) { - throw new ValidationException(this, "voltage cannot be < 0"); - } + private void setV(double v, boolean updateCalculatedBus) { double oldValue = getResource().getAttributes().getV(); if (v != oldValue) { updateResource(res -> res.getAttributes().setV(v)); String variantId = index.getNetwork().getVariantManager().getWorkingVariantId(); index.notifyUpdate(this, "v", variantId, oldValue, v); - // update V for bus in BusView - updateCalculatedBusAttributes(v, getResource().getAttributes().getVoltageLevelId(), this::setVInCalculatedBus); + if (updateCalculatedBus) { + // update V for bus in BusView + updateCalculatedBusAttributes(v, getResource().getAttributes().getVoltageLevelId(), this::setVInCalculatedBus); + } } - return this; + } + + // update without the part setting values in calculated buses otherwise + // it leads to infinite loops because calculated buses also update configured buses + void setConfiguredBusV(double v) { + setV(v, false); } @Override - public Bus setAngle(double angle) { + public Bus setV(double v) { + if (v < 0) { + throw new ValidationException(this, "voltage cannot be < 0"); + } + setV(v, true); + return this; + } + + void setAngle(double angle, boolean updateCalculatedBus) { double oldValue = getResource().getAttributes().getAngle(); if (angle != oldValue) { updateResource(res -> res.getAttributes().setAngle(angle)); String variantId = index.getNetwork().getVariantManager().getWorkingVariantId(); index.notifyUpdate(this, "angle", variantId, oldValue, angle); - // 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; } @@ -124,17 +148,26 @@ private void setAngleInCalculatedBus(CalculatedBusAttributes calculatedBusAttrib 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); - } + 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 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