Skip to content

Commit

Permalink
Synchronization between views for v and angle bus values. (#460)
Browse files Browse the repository at this point in the history
Signed-off-by: HARPER Jon <[email protected]>
Co-authored-by: Franck LECUYER <[email protected]>
  • Loading branch information
jonenst and FranckLecuyer authored Jan 8, 2025
1 parent 6362071 commit 08bd7bf
Show file tree
Hide file tree
Showing 8 changed files with 1,003 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.powsybl.commons.config.PlatformConfig;
import com.powsybl.iidm.network.*;
import com.powsybl.network.store.model.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.jgrapht.Graph;
import org.jgrapht.alg.connectivity.ConnectivityInspector;
import org.jgrapht.graph.DirectedPseudograph;
Expand Down Expand Up @@ -358,6 +360,62 @@ private void setCalculatedBuses(Resource<VoltageLevelAttributes> voltageLevelRes
}
}

private CalculatedBusAttributes findFirstMatchingNodeBreakerCalculatedBusAttributes(Resource<VoltageLevelAttributes> voltageLevelResource,
ConnectedSetResult<T> connectedSet, boolean isBusView) {
// TODO Some day we may decide to start preserving phase/angle values
// in nodebreaker topology even after invalidating the views, so we
// could remove the check for isCalculatedBusesValid. Here it controls
// whether we preserve or not the phase/angle values accross the other
// view. For now we do not preserve to be consistent with the behavior
// of not preserving values from the same view after invalidation.
List<CalculatedBusAttributes> calculatedBusAttributesInOtherView = isBusView ? voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView() : voltageLevelResource.getAttributes().getCalculatedBusesForBusView();
Map<Integer, Integer> nodesToCalculatedBusesInOtherView = isBusView ? voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusBreakerView() : voltageLevelResource.getAttributes().getNodeToCalculatedBusForBusView();
Set<Integer> nodes = (Set<Integer>) connectedSet.getConnectedNodesOrBuses();
if (voltageLevelResource.getAttributes().isCalculatedBusesValid()
&& !CollectionUtils.isEmpty(calculatedBusAttributesInOtherView)
&& !MapUtils.isEmpty(nodesToCalculatedBusesInOtherView)
&& !nodes.isEmpty()) {
// busNumInOtherView is deterministic for the busbreakerview because all busbreakerviewbuses correspond
// to the same busviewbus. For the busview, busNumInOtherView will be non deterministic, it will
// be one of the busbreakerbuses of this busviewbus.
Integer node = nodes.iterator().next();
Integer busNumInOtherView = nodesToCalculatedBusesInOtherView.get(node);
if (busNumInOtherView != null) {
return calculatedBusAttributesInOtherView.get(busNumInOtherView);
}
}
return null;
}

private CalculatedBusAttributes createCalculatedBusAttributesWithVAndAngle(NetworkObjectIndex index,
Resource<VoltageLevelAttributes> voltageLevelResource,
ConnectedSetResult<T> connectedSet,
boolean isBusView) {
double v = Double.NaN;
double angle = Double.NaN;
if (voltageLevelResource.getAttributes().getTopologyKind() == TopologyKind.NODE_BREAKER) {
CalculatedBusAttributes busAttributes = findFirstMatchingNodeBreakerCalculatedBusAttributes(voltageLevelResource, connectedSet, isBusView);
if (busAttributes != null) {
v = busAttributes.getV();
angle = busAttributes.getAngle();
}
} else { // BUS_BREAKER
// currently for busbreakertopology the phase/angle values are preserved
// when set in the busbreakerview which is in a sense always valid.
// So mimic the behavior and always preserve them also in the busview
// by *not* testing for isCalculatedBusesValid.
Set<String> configuredBusesIds = (Set<String>) connectedSet.getConnectedNodesOrBuses();
if (!configuredBusesIds.isEmpty()) {
// nondeterministic, chooses a random configuredbus in this busviewbus
String configuredBusId = configuredBusesIds.iterator().next();
Bus b = index.getConfiguredBus(configuredBusId).orElseThrow(IllegalStateException::new);
v = b.getV();
angle = b.getAngle();
}
}
return new CalculatedBusAttributes(connectedSet.getConnectedVertices(), null, null, v, angle);
}

private CalculationResult<T> getCalculatedBusAttributesList(NetworkObjectIndex index, Resource<VoltageLevelAttributes> voltageLevelResource, boolean isBusView) {
List<CalculatedBusAttributes> calculatedBusAttributesList;
Map<T, Integer> nodeOrBusToCalculatedBusNum;
Expand All @@ -369,7 +427,13 @@ private CalculationResult<T> getCalculatedBusAttributesList(NetworkObjectIndex i
List<ConnectedSetResult<T>> connectedSetList = findConnectedSetList(index, voltageLevelResource, isBusView);
calculatedBusAttributesList = connectedSetList
.stream()
.map(connectedSet -> new CalculatedBusAttributes(connectedSet.getConnectedVertices(), null, null, Double.NaN, Double.NaN))
//TODO in this case in nodebreaker topology we currently don't preserve any values from
//the same view if it was already computed but is invalidated.
//we could do it some day (we need to define good heuristics to
//match previous values to new buses).
//NOTE: We chose to have the same behavior when getting the values from the other view
// get V and Angle values from other view if available
.map(connectedSet -> createCalculatedBusAttributesWithVAndAngle(index, voltageLevelResource, connectedSet, isBusView))
.collect(Collectors.toList());
setCalculatedBuses(voltageLevelResource, isBusView, calculatedBusAttributesList);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
import com.powsybl.iidm.network.util.Networks;
import com.powsybl.network.store.model.*;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;

import java.util.*;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.function.ObjDoubleConsumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -131,10 +135,35 @@ public double getV() {
return getAttributes().getV();
}

private void setVInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) {
calculatedBusAttributes.setV(value);
}

private void setVInConfiguredBus(ConfiguredBusImpl configuredBus, double value) {
configuredBus.setConfiguredBusV(value);
}

private void setAngleInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) {
calculatedBusAttributes.setAngle(value);
}

private void setAngleInConfiguredBus(ConfiguredBusImpl configuredBus, double value) {
configuredBus.setConfiguredBusAngle(value);
}

@Override
public Bus setV(double v) {
getAttributes().setV(v);
index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV);

if (getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) {
// update V in configured buses
// this triggers network notifications for 'v' for each configured bus
updateConfiguredBuses(v, this::setVInConfiguredBus);
} else {
// update V for buses in the other view (busView/busBreakerView)
updateBusesAttributes(v, this::setVInCalculatedBus);
}
return this;
}

Expand All @@ -147,6 +176,15 @@ public double getAngle() {
public Bus setAngle(double angle) {
getAttributes().setAngle(angle);
index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV);

if (getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) {
// update angle in configuredBus
// this triggers network notifications for 'angle' for each configured bus
updateConfiguredBuses(angle, this::setAngleInConfiguredBus);
} else {
// update angle for buses in the other view (busView/busBreakerView)
updateBusesAttributes(angle, this::setAngleInCalculatedBus);
}
return this;
}

Expand Down Expand Up @@ -471,4 +509,62 @@ public <E extends Extension<Bus>, B extends ExtensionAdder<Bus, E>> B newExtensi
public int getCalculatedBusNum() {
return calculatedBusNum;
}

private void updateBusesAttributes(double value, ObjDoubleConsumer<CalculatedBusAttributes> setValue) {
// Use the busnum of this bus to get the nodes in this bus to get the
// busnums in the other view to get the buses of the other view to
// update them all. For the busbreakerview, there is only one matching bus in the busview so return early.
// We only update when isCalculatedBusesValid is true, there is no point in updating stale bus objects and
// in when isCalculatedBusesValid is not true, we may even update the wrong buses (but not much of a problem
// because they are stale objects).
// TODO add tests for updates with isCalculatedBusesValid=false
// NOTE: we don't maintain a mapping from busnum to nodes so we iterate
// all the nodes and filter but it should be ok, the number is small. TODO, is this really ok ?
VoltageLevelAttributes vlAttributes = ((VoltageLevelImpl) getVoltageLevel()).getResource().getAttributes();
Map<Integer, Integer> nodesToCalculatedBuses = isBusView
? vlAttributes.getNodeToCalculatedBusForBusView()
: vlAttributes.getNodeToCalculatedBusForBusBreakerView();
Map<Integer, Integer> nodesToCalculatedBusesInOtherView = isBusView
? vlAttributes.getNodeToCalculatedBusForBusBreakerView()
: vlAttributes.getNodeToCalculatedBusForBusView();
List<CalculatedBusAttributes> calculatedBusAttributes = isBusView
? vlAttributes.getCalculatedBusesForBusBreakerView()
: vlAttributes.getCalculatedBusesForBusView();
if (vlAttributes.isCalculatedBusesValid() && !CollectionUtils.isEmpty(calculatedBusAttributes)
&& !MapUtils.isEmpty(nodesToCalculatedBuses) && !MapUtils.isEmpty(nodesToCalculatedBusesInOtherView)) {
Set<Integer> seen = new HashSet<>();
for (Entry<Integer, Integer> entry : nodesToCalculatedBuses.entrySet()) {
if (getCalculatedBusNum() == entry.getValue()) {
int node = entry.getKey();
Integer busNumInOtherView = nodesToCalculatedBusesInOtherView.get(node);
if (busNumInOtherView != null && !seen.contains(busNumInOtherView)) {
setValue.accept(calculatedBusAttributes.get(busNumInOtherView), value);
index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV);
seen.add(busNumInOtherView);
}
if (!isBusView) {
return;
}
}
}
}
}

private void updateConfiguredBuses(double newValue,
ObjDoubleConsumer<ConfiguredBusImpl> setValue) {
// update all the configured buses
// NOTE: we don't maintain a mapping from busnum to bus so we iterate
// all the buses and filter but it should be ok, the number is small. TODO, is this really ok ?
// We only update when isCalculatedBusesValid is true, otherwise we may update the wrong configured bus
// TODO add tests for updates with isCalculatedBusesValid=false
VoltageLevelAttributes vlAttributes = ((VoltageLevelImpl) getVoltageLevel()).getResource().getAttributes();
if (vlAttributes.isCalculatedBusesValid()) {
for (Entry<String, Integer> entry : vlAttributes.getBusToCalculatedBusForBusView().entrySet()) {
if (getCalculatedBusNum() == entry.getValue()) {
ConfiguredBusImpl bus = index.getConfiguredBus(entry.getKey()).orElseThrow(IllegalStateException::new);
setValue.accept(bus, newValue);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,18 @@
import com.powsybl.iidm.network.ValidationException;
import com.powsybl.iidm.network.VoltageLevel;
import com.powsybl.iidm.network.VscConverterStation;
import com.powsybl.network.store.model.AttributeFilter;
import com.powsybl.network.store.model.CalculatedBusAttributes;
import com.powsybl.network.store.model.ConfiguredBusAttributes;
import com.powsybl.network.store.model.VoltageLevelAttributes;
import com.powsybl.network.store.model.Resource;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.ObjDoubleConsumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -77,31 +84,97 @@ public double getAngle() {
return getResource().getAttributes().getAngle();
}

@Override
public Bus setV(double v) {
if (v < 0) {
throw new ValidationException(this, "voltage cannot be < 0");
}
private void setV(double v, boolean updateCalculatedBus) {
double oldValue = getResource().getAttributes().getV();
if (v != oldValue) {
updateResource(res -> res.getAttributes().setV(v));
String variantId = index.getNetwork().getVariantManager().getWorkingVariantId();
index.notifyUpdate(this, "v", variantId, oldValue, v);

if (updateCalculatedBus) {
// update V for bus in BusView
updateCalculatedBusAttributes(v, getResource().getAttributes().getVoltageLevelId(), this::setVInCalculatedBus);
}
}
return this;
}

// update without the part setting values in calculated buses otherwise it
// leads to infinite loops because calculated buses also update configured buses
void setConfiguredBusV(double v) {
setV(v, false);
}

@Override
public Bus setAngle(double angle) {
public Bus setV(double v) {
if (v < 0) {
throw new ValidationException(this, "voltage cannot be < 0");
}
setV(v, true);
return this;
}

void setAngle(double angle, boolean updateCalculatedBus) {
double oldValue = getResource().getAttributes().getAngle();
if (angle != oldValue) {
updateResource(res -> res.getAttributes().setAngle(angle));
String variantId = index.getNetwork().getVariantManager().getWorkingVariantId();
index.notifyUpdate(this, "angle", variantId, oldValue, angle);

if (updateCalculatedBus) {
// update angle for bus in BusView
updateCalculatedBusAttributes(angle, getResource().getAttributes().getVoltageLevelId(), this::setAngleInCalculatedBus);
}
}
}

// update without the part setting values in calculated buses otherwise
// it leads to infinite loops because calculated buses also update configured buses
void setConfiguredBusAngle(double angle) {
setAngle(angle, false);
}

@Override
public Bus setAngle(double angle) {
setAngle(angle, true);
return this;
}

private void setVInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) {
calculatedBusAttributes.setV(value);
}

private void setAngleInCalculatedBus(CalculatedBusAttributes calculatedBusAttributes, double value) {
calculatedBusAttributes.setAngle(value);
}

private void updateCalculatedBusAttributes(double newValue,
String voltageLevelId,
ObjDoubleConsumer<CalculatedBusAttributes> setValue) {
VoltageLevelImpl voltageLevel = index.getVoltageLevel(voltageLevelId).orElseThrow(IllegalArgumentException::new);
VoltageLevelAttributes vlAttributes = voltageLevel.getResource().getAttributes();
Map<String, Integer> calculatedBuses = vlAttributes.getBusToCalculatedBusForBusView();
List<CalculatedBusAttributes> busViewCalculatedBusesAttributes = vlAttributes.getCalculatedBusesForBusView();
// We only update when isCalculatedBusesValid is true, there is no point in updating stale bus objects and
// in when isCalculatedBusesValid is not true, we may even update the wrong buses (but not much of a problem
// because they are stale objects).
// TODO add tests for updates with isCalculatedBusesValid=false
if (vlAttributes.isCalculatedBusesValid()
&& !CollectionUtils.isEmpty(busViewCalculatedBusesAttributes)
&& !MapUtils.isEmpty(calculatedBuses)) {
Integer busviewnum = calculatedBuses.get(getId());
if (busviewnum != null) {
CalculatedBusAttributes busViewCalculatedBusAttributes = busViewCalculatedBusesAttributes.get(busviewnum);
// Same code as the iidm impl for CalculatedBus setV / setAngle
// (without the part setting values in configured buses otherwise
// it would be an infinite loop), but copy paste here
// to avoid creating the object (calculated buses are created on when computing
// the bus view, but we want to only update if the busview exist, not force its creation)
setValue.accept(busViewCalculatedBusAttributes, newValue);
index.updateVoltageLevelResource(voltageLevel.getResource(), AttributeFilter.SV);
}
}
}

@Override
public double getFictitiousP0() {
return getResource().getAttributes().getFictitiousP0();
Expand Down
Loading

0 comments on commit 08bd7bf

Please sign in to comment.