Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix syncv #460

Merged
merged 21 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a43f002
Synchronization between views for v and angle bus values.
FranckLecuyer Sep 13, 2024
8492ef9
Merge branch 'main' into synchronize_set_v_angle_on_bus_between_views
FranckLecuyer Sep 17, 2024
3afafdd
Merge branch 'main' into synchronize_set_v_angle_on_bus_between_views
FranckLecuyer Sep 19, 2024
9ce5cb7
Merge branch 'main' into synchronize_set_v_angle_on_bus_between_views
FranckLecuyer Sep 24, 2024
6661c1f
Changes after review
FranckLecuyer Sep 24, 2024
3b26409
Fix sonar issue
FranckLecuyer Sep 24, 2024
98bf508
Merge branch 'main' into synchronize_set_v_angle_on_bus_between_views
FranckLecuyer Sep 25, 2024
4c2dffb
more exhaustive test
jonenst Sep 27, 2024
d09854e
wip fix syncv
jonenst Sep 27, 2024
ed42836
more fixes using busnum instead of vertex
jonenst Sep 30, 2024
54ef522
Fix updateBusesAttributes method in CalculatedBus.java
FranckLecuyer Sep 30, 2024
d59aada
Cleaning and optimizing code
FranckLecuyer Oct 2, 2024
ebe387e
Move methods in another file
FranckLecuyer Oct 3, 2024
16edded
Add configuration similar to the one in AbstractMainConnectedComponen…
FranckLecuyer Oct 3, 2024
15df0fa
Clean code in getVAndAngleFromOtherView method
FranckLecuyer Oct 4, 2024
6853516
VoltageLevelTest, Add more combinations to setV/setAngle for cache ex…
jonenst Oct 22, 2024
9c17fe0
Improve exhaustive test, make nodebreaker consistent with invalid vie…
jonenst Dec 4, 2024
e9f18ad
add more checks for isCalculatedBusValid for updates and code improve…
jonenst Dec 26, 2024
02033a4
remove useless cast as per sonar
jonenst Dec 26, 2024
101cb6f
Merge branch 'main' into fixsyncv
jonenst Dec 28, 2024
a824b15
Merge branch 'main' into fixsyncv
jonenst Jan 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use if (isCalculatedBusesValid(voltageLevelResource, !isBusView)) {
like in getCalculatedBusAttributesList ?

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);
}
Comment on lines +138 to +152
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have some consistency in naming?
setAngleInConfiguredBus // setConfiguredBusAngle
setAngleInCalculatedBus // setAngle
same for V


@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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private?

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
Loading