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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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,52 @@ 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 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> calculatedBusAttributes = isBusView ? voltageLevelResource.getAttributes().getCalculatedBusesForBusBreakerView() : voltageLevelResource.getAttributes().getCalculatedBusesForBusView();
if (!CollectionUtils.isEmpty(calculatedBusAttributes)) {
Map<Integer, Integer> 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 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 values are always preserved
// when using only the busbreakerview. So mimic the behavior and always preserve them
// when using the busview: get V and Angle values from configured buses
String configuredBusId = ((ConnectedSetResult<String>) connectedSet).getConnectedNodesOrBuses().iterator().next(); // nondeterministic
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe check iterator with hasNext before calling next ...

Bus b = index.getConfiguredBus(configuredBusId).orElseThrow();
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe customize the exception, in order to have a more accurate message ...

v = b.getV();
angle = b.getAngle();
}
return new CalculatedBusAttributes(connectedSet.getConnectedVertices(), null, null, v, angle);
Comment on lines +385 to +406
Copy link
Collaborator

Choose a reason for hiding this comment

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

This if/else makes me think that this should be implemented in BusBreakerTopology/NodeBreakerTopology classes ? Maybe an abstraction here

}

private CalculationResult<T> getCalculatedBusAttributesList(NetworkObjectIndex index, Resource<VoltageLevelAttributes> voltageLevelResource, boolean isBusView) {
List<CalculatedBusAttributes> calculatedBusAttributesList;
Map<T, Integer> nodeOrBusToCalculatedBusNum;
Expand All @@ -369,7 +417,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,43 @@ 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) {
// busnum of this bus -> nodes in this bus -> busnums in the other view -> buses of the other view
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment is not clear

VoltageLevelAttributes vlAttributes = ((VoltageLevelImpl) getVoltageLevel()).getResource().getAttributes();
Map<Integer, Integer> nodesToCalculatedBuses = isBusView
? vlAttributes.getNodeToCalculatedBusForBusView()
: vlAttributes.getNodeToCalculatedBusForBusBreakerView();
Map<Integer, Integer> nodesToCalculatedBusesInOtherView = isBusView
? vlAttributes.getNodeToCalculatedBusForBusBreakerView()
: vlAttributes.getNodeToCalculatedBusForBusView();
if (!MapUtils.isEmpty(nodesToCalculatedBuses) && !MapUtils.isEmpty(nodesToCalculatedBusesInOtherView)) {
for (Entry<Integer, Integer> entry : nodesToCalculatedBuses.entrySet()) {
if (getCalculatedBusNum() == entry.getValue()) {
int node = entry.getKey();
if (nodesToCalculatedBusesInOtherView.containsKey(node)) {
int busNumInOtherView = nodesToCalculatedBusesInOtherView.get(node);
List<CalculatedBusAttributes> calculatedBusAttributes = isBusView
? vlAttributes.getCalculatedBusesForBusBreakerView()
: vlAttributes.getCalculatedBusesForBusView();
Comment on lines +528 to +530
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this inside the for loop? It looks like it's constant? Similarly to nodesToCalculatedBuses, etc

if (!CollectionUtils.isEmpty(calculatedBusAttributes)) {
setValue.accept(calculatedBusAttributes.get(busNumInOtherView), value);
index.updateVoltageLevelResource(voltageLevelResource, AttributeFilter.SV);
}
}
}
}
}
}

private void updateConfiguredBuses(double newValue,
ObjDoubleConsumer<ConfiguredBusImpl> setValue) {
VoltageLevelAttributes vlAttributes = ((VoltageLevelImpl) getVoltageLevel()).getResource().getAttributes();
for (Entry<String, Integer> entry : vlAttributes.getBusToCalculatedBusForBusView().entrySet()) {
if (getCalculatedBusNum() == entry.getValue()) {
Bus bus = getVoltageLevel().getBusBreakerView().getBus(entry.getKey());
setValue.accept((ConfiguredBusImpl) bus, newValue);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@
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.MapUtils;

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 +82,94 @@ 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();
Map<String, Integer> calculatedBuses = voltageLevel.getResource().getAttributes().getBusToCalculatedBusForBusView();
// TODO, do we want to update the busview values if the view is invalid ?
// if invalid, values will be restored from the configuredbuses on the next
// busview computation anyway in the new bus objects and attributes,
// but the previous bus objects and attributes may still be used somewhere
// so there is a visible effect to choosing to update invalid views or not.
if (!MapUtils.isEmpty(calculatedBuses)) {
Integer busviewnum = calculatedBuses.get(getId());
if (busviewnum != null) {
CalculatedBusAttributes busviewattributes = voltageLevel.getResource().getAttributes().getCalculatedBusesForBusView().get(busviewnum);
// Same code as the iidm impl for CalculatedBus setV / setAngle
// (without the part setting values in configured buses otherwise
// it would be an infinite loop), but copy paste here
// to avoid creating the object (calculated buses are created on when computing
// the bus view, but we want to only update if the busview exist, not force its creation)
setValue.accept(busviewattributes, newValue);
index.updateVoltageLevelResource(voltageLevel.getResource(), AttributeFilter.SV);
}
}
}

@Override
public double getFictitiousP0() {
return getResource().getAttributes().getFictitiousP0();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,10 @@
import com.powsybl.iidm.network.*;
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.*;
Expand All @@ -33,58 +29,6 @@
* @author Etienne Homer <etienne.homer at rte-france.com>
*/
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<Integer, TemporaryLimitAttributes> 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();
Expand Down
Loading
Loading