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

CGMES: adjust multiple unpaired dangling lines connected at same boundary node #2737

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2023, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.cgmes.conformity;

import com.powsybl.cgmes.model.GridModelReference;
import com.powsybl.cgmes.model.GridModelReferenceResources;
import com.powsybl.commons.datasource.ResourceSet;

/**
* @author Luma Zamarreño <zamarrenolm at aia.es>
*/
public final class CgmesConformity3ModifiedCatalog {

private CgmesConformity3ModifiedCatalog() {
}

public static GridModelReference microGridBE3DanglingLinesSameBoundary1Disconnected() {
String base = ENTSOE_CONFORMITY_3_MODIFIED
+ "/MicroGrid/BE-3dls-same-boundary-node-1disconnected/";
return new GridModelReferenceResources(
"MicroGrid-BE-3dls-same-boundary-node-1disconnected",
null,
new ResourceSet(base,
"20210325T1530Z_1D_BE_EQ_001_3dls_1disconnected.xml",
"20210325T1530Z_1D_BE_SSH_001_3dls_1disconnected.xml"
),
CgmesConformity3Catalog.microGridBaseCaseBoundaries());
}

private static final String ENTSOE_CONFORMITY_3_MODIFIED = "/conformity-modified/cas-3-data-3.0.2";
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,17 @@ public static void danglingLineDisconnectedAtBoundaryHasBeenDisconnectedReport(R
.withSeverity(TypedValue.WARN_SEVERITY)
.build());
}

public static void multipleUnpairedDanglingLinesAtSameBoundaryReport(Reporter reporter, String danglingLineId, double p0, double q0, double p0Adjusted, double q0Adjusted) {
reporter.report(Report.builder()
.withKey("multipleUnpairedDanglingLinesAtSameBoundary")
.withDefaultMessage("Multiple unpaired DanglingLines were connected at the same boundary side. Adjusted original injection from (${p0}, ${q0}) to (${p0Adjusted}, ${q0Adjusted}) for dangling line ${danglingLineId}.")
.withValue("danglingLineId", danglingLineId)
.withValue("p0", p0)
.withValue("q0", q0)
.withValue("p0Adjusted", p0Adjusted)
.withValue("q0Adjusted", q0Adjusted)
.withSeverity(TypedValue.WARN_SEVERITY)
.build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import static com.powsybl.cgmes.conversion.CgmesReports.importedCgmesNetworkReport;
import static com.powsybl.cgmes.conversion.Conversion.Config.StateProfile.SSH;
import static java.util.stream.Collectors.groupingBy;

/**
* TwoWindingsTransformer Interpretation
Expand Down Expand Up @@ -240,6 +241,7 @@ public Network convert(Reporter reporter) {

// apply post-processors
handleDangingLineDisconnectedAtBoundary(network, context);
adjustMultipleUnpairedDanglingLinesAtSameBoundaryNode(network, context);
for (CgmesImportPostProcessor postProcessor : postProcessors) {
// FIXME generic cgmes models may not have an underlying triplestore
// TODO maybe pass the properties to the post processors
Expand Down Expand Up @@ -281,6 +283,46 @@ private void handleDangingLineDisconnectedAtBoundary(Network network, Context co
}
}

private void adjustMultipleUnpairedDanglingLinesAtSameBoundaryNode(Network network, Context context) {
network.getDanglingLineStream(DanglingLineFilter.UNPAIRED)
.filter(dl -> dl.getTerminal().isConnected())
.collect(groupingBy(Conversion::getDanglingLineBoundaryNode))
.values().stream()
// Only perform adjustment for the groups with more than one connected dangling line
.filter(dls -> dls.size() > 1)
.forEach(dls -> adjustMultipleUnpairedDanglingLinesAtSameBoundaryNode(dls, context));
}

private void adjustMultipleUnpairedDanglingLinesAtSameBoundaryNode(List<DanglingLine> dls, Context context) {
Copy link
Member

@annetill annetill Oct 4, 2023

Choose a reason for hiding this comment

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

After a talk with my colleague, it seems that it is not the good solution because the merge will not work. Imagine we have 3 DLs with the same pairing key (1 disconnected and 2 connected). On the other side, we have 2 DLs with the same pairing key (both connected). If they are disconnected, they are excluded from the merging. If they are connected, we do nothing without any error message. I think we have to:

  1. Warn the user during the merge in order to create by hand the remaining tie lines ;
  2. Or :
  • Put all the P0 and Q0 on one DL that keep the good pairing key ;
  • For the other ones connected, we put zero and we change the pairing key (with #0 or something else). But which one chosen ? And on the other side ? The merge will be done in creating a tie line of 2 arbitrary dangling lines (and maybe not the good ones).

I am lost :-)

Copy link
Member Author

Choose a reason for hiding this comment

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

But the merge scenario you discuss has nothing to do with this change.
This change just improves on how do you see the ONLY equivalent injection that exists in one IGM, when you have MULTIPLE (all connected) dangling lines.
I would discuss separately the function required for perform a merge when multiple dangling lines from each side exist at a single boundary point.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the cleanest solution for the scenario you describe is that the user creates a modified boundary file, with a new connectivity/topological node. The user MUST then specify which lines go to each boundary node, in a way that always leaves only one line of each IGM at each boundary node.

If we do not want to modify the boundary file and the IGM to use the new (not "official" boundary point), then we can arbitrarily try to merge any pair of candidates or let the user configure it in some way. But this seems quite "dirty" for me: what happens if we have two connected lines in one side and only one in the other? How do we let the user specify the "correct" pairs to be merged? a parameter?

Copy link
Member Author

Choose a reason for hiding this comment

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

Going a little bit further about merging multiple dangling lines in the same node:

We can try to pair the dangling lines from each side that have "closer values" of (p0, q0) (similar equivalent injection).
Following this idea, If there are 2 lines on one side and only one in the other: a tie line will be created, and a dangling line will be left on one side.

What do you think of this alternative ?
In any case, I think these considerations should go in a separate PR.

Copy link
Member

Choose a reason for hiding this comment

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

We are going to open a new PR to increase merge logging, in order to warn the user that he has to do something!

// All dangling lines will have same value for p0, q0. Take it from the first one
double p0 = dls.get(0).getP0();
double q0 = dls.get(0).getQ0();
// Divide this value between all connected dangling lines
// This method is called only if there is more than 1 connected dangling line
long count = dls.size();
final double p0Adjusted = p0 / count;
final double q0Adjusted = q0 / count;
dls.forEach(dl -> {
LOG.warn("Multiple unpaired DanglingLines were connected at the same boundary side. Adjusted original injection from ({}, {}) to ({}, {}) for dangling line {}.", p0, q0, p0Adjusted, q0Adjusted, dl.getId());
CgmesReports.multipleUnpairedDanglingLinesAtSameBoundaryReport(context.getReporter(), dl.getId(), p0, q0, p0Adjusted, q0Adjusted);
dl.setP0(p0Adjusted);
dl.setQ0(q0Adjusted);
});
}

public static String getDanglingLineBoundaryNode(DanglingLine dl) {
String node;
node = dl.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.CONNECTIVITY_NODE_BOUNDARY);
if (node == null) {
node = dl.getProperty(Conversion.CGMES_PREFIX_ALIAS_PROPERTIES + CgmesNames.TOPOLOGICAL_NODE_BOUNDARY);
}
if (node == null) {
LOG.warn("Dangling line {} does not have a boundary node identifier.", dl.getId());
node = "unknown";
}
return node;
}

private Source isBoundaryBaseVoltage(String graph) {
//There are unit tests where the boundary file contains the sequence "EQBD" and others "EQ_BD"
return graph.contains("EQ") && graph.contains("BD") ? Source.BOUNDARY : Source.IGM;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
import com.google.common.jimfs.Jimfs;
import com.powsybl.cgmes.conformity.Cgmes3Catalog;
import com.powsybl.cgmes.conformity.Cgmes3ModifiedCatalog;
import com.powsybl.cgmes.conformity.CgmesConformity3Catalog;
import com.powsybl.cgmes.conformity.CgmesConformity3ModifiedCatalog;
import com.powsybl.cgmes.conversion.CgmesModelExtension;
import com.powsybl.cgmes.conversion.Conversion;
import com.powsybl.iidm.network.Importers;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.Substation;
Expand Down Expand Up @@ -63,5 +66,43 @@ void microGridGeographicalRegionInBoundary() {
assertEquals("BE", brussels.getNullableCountry().toString());
}

@Test
void testMultipleUnpairedLinesAtSameBoundary() {
// Dangling lines at Brussels substation
final String line5Id = "b18cd1aa-7808-49b9-a7cf-605eaf07b006";
final String line4Id = "ed0c5d75-4a54-43c8-b782-b20d7431630b";
final String line3Id = "78736387-5f60-4832-b3fe-d50daf81b0a6";
final String nodeGY11 = "b67c8340-cb6e-11e1-bcee-406c8f32ef58";
final String nodeMA11 = "b67cf870-cb6e-11e1-bcee-406c8f32ef58";
final String nodeAL11 = "b675a570-cb6e-11e1-bcee-406c8f32ef58";

// In the base case each line is connected to a different boundary node
// All have different values of p0, q0
Network networkBase = Network.read(CgmesConformity3Catalog.microGridBaseCaseBE().dataSource());
assertEquals(nodeGY11, Conversion.getDanglingLineBoundaryNode(networkBase.getDanglingLine(line5Id)));
assertEquals(-27.0286, networkBase.getDanglingLine(line5Id).getP0(), 1e-4);
assertEquals(120.7887, networkBase.getDanglingLine(line5Id).getQ0(), 1e-4);
assertEquals(nodeMA11, Conversion.getDanglingLineBoundaryNode(networkBase.getDanglingLine(line4Id)));
assertEquals(-8.9532, networkBase.getDanglingLine(line4Id).getP0(), 1e-4);
assertEquals(67.2335, networkBase.getDanglingLine(line4Id).getQ0(), 1e-4);
assertEquals(nodeAL11, Conversion.getDanglingLineBoundaryNode(networkBase.getDanglingLine(line3Id)));
assertEquals(-14.0675, networkBase.getDanglingLine(line3Id).getP0(), 1e-4);
assertEquals(63.9583, networkBase.getDanglingLine(line3Id).getQ0(), 1e-4);

// We have prepared a modified case where lines 4 and 5 both connect to the same node
// p0, q0 is adjusted for connected dangling lines
// p0, q0 of the disconnected is not modified
Network network3dls = Network.read(CgmesConformity3ModifiedCatalog.microGridBE3DanglingLinesSameBoundary1Disconnected().dataSource());
assertEquals(nodeGY11, Conversion.getDanglingLineBoundaryNode(network3dls.getDanglingLine(line5Id)));
assertEquals(-13.5143, network3dls.getDanglingLine(line5Id).getP0(), 1e-4);
assertEquals(60.3944, network3dls.getDanglingLine(line5Id).getQ0(), 1e-4);
assertEquals(nodeGY11, Conversion.getDanglingLineBoundaryNode(network3dls.getDanglingLine(line4Id)));
assertEquals(-13.5143, network3dls.getDanglingLine(line4Id).getP0(), 1e-4);
assertEquals(60.3944, network3dls.getDanglingLine(line4Id).getQ0(), 1e-4);
assertEquals(nodeGY11, Conversion.getDanglingLineBoundaryNode(network3dls.getDanglingLine(line3Id)));
assertEquals(-27.0286, network3dls.getDanglingLine(line3Id).getP0(), 1e-4);
assertEquals(120.7887, network3dls.getDanglingLine(line3Id).getQ0(), 1e-4);
}

private FileSystem fileSystem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ public static void findAndAssociateDanglingLines(DanglingLine candidateDanglingL
List<DanglingLine> connectedDls = dls.stream().filter(dl -> dl.getTerminal().isConnected()).collect(Collectors.toList());
if (connectedDls.size() == 1) { // if there is exactly one connected dangling line in the merging network, merge it. Otherwise, do nothing
associateDanglingLines.accept(connectedDls.get(0), candidateDanglingLine);
} else {
LOGGER.warn("Several connected dangling lines {} of the same subnetwork are candidate for merging for pairing key '{}'. " +
"No tie line automatically created, tie lines must be created by hand.",
connectedDls.stream().map(DanglingLine::getId).collect(Collectors.toList()), connectedDls.get(0).getPairingKey());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -619,23 +619,61 @@ public void mergeThenCloneVariantBug() {

@Test
public void multipleDanglingLinesInMergedNetwork() {
// The code is not the same if the dangling line with the duplicate pairing key is in the current network
// or in the network we are adding when we try to associate dangling lines.
//
// This test covers the case where the duplicate pairing key is in the CURRENT network.
addCommonSubstationsAndVoltageLevels();
addCommonDanglingLines("dl1", "code", "dl2", "code");
addDanglingLine(n2, "vl2", "dl3", "code", "b2", null);
Network merge = Network.merge(n1, n2);
// Since n2 has ONLY ONE connected dangling line with the pairing key, the tie line is created
assertNotNull(merge.getTieLine("dl1 + dl2"));
assertEquals("dl1_name + dl2_name", merge.getTieLine("dl1 + dl2").getOptionalName().orElse(null));
assertEquals("dl1_name + dl2_name", merge.getTieLine("dl1 + dl2").getNameOrId());
}

@Test
public void multipleConnectedDanglingLinesInMergedNetwork() {
// The code is not the same if the dangling line with the duplicate pairing key is in the current network
// or in the network we are adding when we try to associate dangling lines.
//
// This test covers the case where the duplicate pairing key is in the CURRENT network.
addCommonSubstationsAndVoltageLevels();
addCommonDanglingLines("dl1", "code", "dl2", "code");
addDanglingLine(n2, "vl2", "dl3", "code", "b2", "b2");
Network merge = Network.merge(n1, n2);
// Since n2 has SEVERAL connected dangling lines with the pairing key, we don't create the tie line
assertEquals(0, merge.getTieLineCount());
}

@Test
public void multipleDanglingLinesInMergingNetwork() {
// The code is not the same if the dangling line with the duplicate pairing key is in the current network
// or in the network we are adding when we try to associate dangling lines.
//
// This test covers the case where the duplicate pairing key is in the ADDED network.
addCommonSubstationsAndVoltageLevels();
addCommonDanglingLines("dl1", "code", "dl2", "code");
addDanglingLine(n1, "vl1", "dl3", "code", "b1", null);
Network merge = Network.merge(n1, n2);
// Since n1 has ONLY ONE connected dangling line with the pairing key, the tie line is created
assertNotNull(merge.getTieLine("dl1 + dl2"));
assertEquals("dl1_name + dl2_name", merge.getTieLine("dl1 + dl2").getOptionalName().orElse(null));
assertEquals("dl1_name + dl2_name", merge.getTieLine("dl1 + dl2").getNameOrId());
}

@Test
public void multipleConnectedDanglingLinesWithSamePairingKey() {
// The code is not the same if the dangling line with the duplicate pairing key is in the current network
// or in the network we are adding when we try to associate dangling lines.
//
// This test covers the case where the duplicate pairing key is in the ADDED network.
addCommonSubstationsAndVoltageLevels();
addCommonDanglingLines("dl1", "code", "dl2", "code");
addDanglingLine(n1, "vl1", "dl3", "code", "b1", "b1");
Network merge = Network.merge(n1, n2);
// Since n1 has SEVERAL connected dangling lines with the pairing key, we don't create the tie line
assertEquals(0, merge.getTieLineCount());
}
}