From 473d382c1d474a9504e924c90f5e366791a0050e Mon Sep 17 00:00:00 2001 From: Caio Luke <31912369+caioluke@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:06:17 +0200 Subject: [PATCH 1/3] Fix DanglingLine boundary flows computation (#3169) * don't split shunt admittance in dangling lines * fix broken test * fix sonar "Only one method invocation is expected when testing runtime exceptions" * Simplifying Signed-off-by: Caio Luke Co-authored-by: Damien Jeandemange --- ...eurostag-tutorial-example1-branches-tl.txt | 8 +-- .../util/DanglingLineBoundaryImpl.java | 17 ++++--- .../iidm/network/util/DanglingLineData.java | 24 +++------ .../network/util/DanglingLineDataTest.java | 4 +- .../iidm/network/impl/util/SVTest.java | 8 +-- .../src/test/resources/V1_0/tieline.xml | 4 +- .../test/resources/V1_0/tl-loading-limits.xml | 4 +- .../src/test/resources/V1_1/tieline.xml | 4 +- .../test/resources/V1_1/tl-loading-limits.xml | 4 +- .../src/test/resources/V1_2/tieline.xml | 4 +- .../test/resources/V1_2/tl-loading-limits.xml | 4 +- .../src/test/resources/V1_3/tieline.xml | 4 +- .../resources/V1_3/tielineWithAliases.xml | 4 +- .../test/resources/V1_3/tl-loading-limits.xml | 4 +- .../src/test/resources/V1_4/tieline.xml | 4 +- .../resources/V1_4/tielineWithAliases.xml | 4 +- .../test/resources/V1_4/tl-loading-limits.xml | 4 +- .../iidm/network/tck/AbstractAreaTest.java | 24 ++++----- .../network/tck/AbstractMergeNetworkTest.java | 8 +-- .../iidm/network/tck/AbstractTieLineTest.java | 49 ++++++++++--------- .../src/test/resources/be.json | 20 ++++---- 21 files changed, 101 insertions(+), 109 deletions(-) diff --git a/ampl-converter/src/test/resources/inputs/eurostag-tutorial-example1-branches-tl.txt b/ampl-converter/src/test/resources/inputs/eurostag-tutorial-example1-branches-tl.txt index 664c7b14587..15c9e03dd55 100644 --- a/ampl-converter/src/test/resources/inputs/eurostag-tutorial-example1-branches-tl.txt +++ b/ampl-converter/src/test/resources/inputs/eurostag-tutorial-example1-branches-tl.txt @@ -1,8 +1,8 @@ #Branches (sim1/InitialState) #"variant" "num" "bus1" "bus2" "3wt num" "sub.1" "sub.2" "r (pu)" "x (pu)" "g1 (pu)" "g2 (pu)" "b1 (pu)" "b2 (pu)" "cst ratio (pu)" "ratio tc" "phase tc" "p1 (MW)" "p2 (MW)" "q1 (MVar)" "q2 (MVar)" "patl1 (A)" "patl2 (A)" "merged" "fault" "curative" "id" "description" -1 2 2 5 -1 2 5 0.00103878 0.0138504 0.000722000 0.000722000 0.139346 0.139346 1.00000 -1 -1 302.444 -301.316 98.7403 -116.525 100.000 -99999.0 false 0 0 "NHV1_XNODE1" "NHV1_XNODE1" -1 3 5 3 -1 5 3 0.00103878 0.00900277 0.000722000 0.000722000 0.139346 0.139346 1.00000 -1 -1 301.782 -300.434 116.442 -137.188 -99999.0 100.000 false 0 0 "XNODE1_NHV2" "XNODE1_NHV2" -1 5 2 6 -1 2 6 0.00103878 0.0138504 0.000722000 0.000722000 0.139346 0.139346 1.00000 -1 -1 302.444 -301.316 98.7403 -116.525 100.000 -99999.0 false 0 0 "NHV1_XNODE2" "NHV1_XNODE2" -1 6 6 3 -1 6 3 0.00103878 0.00900277 0.000722000 0.000722000 0.139346 0.139346 1.00000 -1 -1 301.782 -300.434 116.442 -137.188 -99999.0 100.000 false 0 0 "XNODE2_NHV2" "XNODE2_NHV2" +1 2 2 5 -1 2 5 0.00103878 0.0138504 0.000722000 0.000722000 0.139346 0.139346 1.00000 -1 -1 302.444 -301.278 98.7403 -116.563 100.000 -99999.0 false 0 0 "NHV1_XNODE1" "NHV1_XNODE1" +1 3 5 3 -1 5 3 0.00103878 0.00900277 0.000722000 0.000722000 0.139346 0.139346 1.00000 -1 -1 301.745 -300.434 116.567 -137.188 -99999.0 100.000 false 0 0 "XNODE1_NHV2" "XNODE1_NHV2" +1 5 2 6 -1 2 6 0.00103878 0.0138504 0.000722000 0.000722000 0.139346 0.139346 1.00000 -1 -1 302.444 -301.278 98.7403 -116.563 100.000 -99999.0 false 0 0 "NHV1_XNODE2" "NHV1_XNODE2" +1 6 6 3 -1 6 3 0.00103878 0.00900277 0.000722000 0.000722000 0.139346 0.139346 1.00000 -1 -1 301.745 -300.434 116.567 -137.188 -99999.0 100.000 false 0 0 "XNODE2_NHV2" "XNODE2_NHV2" 1 7 1 2 -1 1 2 0.000184615 0.00769009 0.00000 0.00000 0.00000 0.00000 1.05263 -1 -1 605.558 -604.891 225.283 -197.480 -99999.0 -99999.0 false 0 0 "NGEN_NHV1" "NGEN_NHV1" 1 8 3 4 -1 3 4 0.000210000 0.0179988 0.00000 0.00000 0.00000 0.00000 1.00067 1 -1 600.868 -600.000 274.377 -200.000 -99999.0 -99999.0 false 0 0 "NHV2_NLOAD" "NHV2_NLOAD" diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java index 4aa5a2f8f8b..bf68d246b1f 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineBoundaryImpl.java @@ -17,8 +17,9 @@ * @author Miora Ralambotiana {@literal } */ public class DanglingLineBoundaryImpl implements Boundary { - // for SV use: side represents the network side, that is always - // Side.ONE for a dangling line. + // Notes about SV utility class usage here: + // - side represents the network side, which is always Side.ONE for a dangling line. + // - DanglingLine model has shunt admittance on network side only, hence splitShuntAdmittance argument in SV methods must be set to false. private final DanglingLine parent; @@ -29,7 +30,7 @@ public DanglingLineBoundaryImpl(DanglingLine parent) { @Override public double getV() { if (useHypothesis(parent)) { - DanglingLineData danglingLineData = new DanglingLineData(parent, true); + DanglingLineData danglingLineData = new DanglingLineData(parent); return danglingLineData.getBoundaryBusU(); } @@ -38,14 +39,14 @@ public double getV() { if (zeroImpedance(parent)) { return getV(b); } else { - return new SV(t.getP(), t.getQ(), getV(b), getAngle(b), TwoSides.ONE).otherSideU(parent, true); + return new SV(t.getP(), t.getQ(), getV(b), getAngle(b), TwoSides.ONE).otherSideU(parent, false); } } @Override public double getAngle() { if (useHypothesis(parent)) { - DanglingLineData danglingLineData = new DanglingLineData(parent, true); + DanglingLineData danglingLineData = new DanglingLineData(parent); return Math.toDegrees(danglingLineData.getBoundaryBusTheta()); } Terminal t = parent.getTerminal(); @@ -53,7 +54,7 @@ public double getAngle() { if (zeroImpedance(parent)) { return getAngle(b); } else { - return new SV(t.getP(), t.getQ(), getV(b), getAngle(b), TwoSides.ONE).otherSideA(parent, true); + return new SV(t.getP(), t.getQ(), getV(b), getAngle(b), TwoSides.ONE).otherSideA(parent, false); } } @@ -67,7 +68,7 @@ public double getP() { if (zeroImpedance(parent)) { return -t.getP(); } else { - return new SV(t.getP(), t.getQ(), getV(b), getAngle(b), TwoSides.ONE).otherSideP(parent, true); + return new SV(t.getP(), t.getQ(), getV(b), getAngle(b), TwoSides.ONE).otherSideP(parent, false); } } @@ -81,7 +82,7 @@ public double getQ() { if (zeroImpedance(parent)) { return -t.getQ(); } else { - return new SV(t.getP(), t.getQ(), getV(b), getAngle(b), TwoSides.ONE).otherSideQ(parent, true); + return new SV(t.getP(), t.getQ(), getV(b), getAngle(b), TwoSides.ONE).otherSideQ(parent, false); } } diff --git a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineData.java b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineData.java index 07fb9c9a9bd..c67cad5acf3 100644 --- a/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineData.java +++ b/iidm/iidm-api/src/main/java/com/powsybl/iidm/network/util/DanglingLineData.java @@ -26,10 +26,6 @@ public class DanglingLineData { private final double boundaryBusTheta; public DanglingLineData(DanglingLine danglingLine) { - this(danglingLine, true); - } - - public DanglingLineData(DanglingLine danglingLine, boolean splitShuntAdmittance) { this.danglingLine = Objects.requireNonNull(danglingLine); double u1 = getV(danglingLine); @@ -47,32 +43,26 @@ public DanglingLineData(DanglingLine danglingLine, boolean splitShuntAdmittance) return; } - double g1 = splitShuntAdmittance ? danglingLine.getG() * 0.5 : danglingLine.getG(); - double b1 = splitShuntAdmittance ? danglingLine.getB() * 0.5 : danglingLine.getB(); - double g2 = splitShuntAdmittance ? danglingLine.getG() * 0.5 : 0.0; - double b2 = splitShuntAdmittance ? danglingLine.getB() * 0.5 : 0.0; - Complex v1 = ComplexUtils.polar2Complex(u1, theta1); + // DanglingLine model has shunt admittance on network side only, so it is not split between both sides. Complex vBoundaryBus = new Complex(Double.NaN, Double.NaN); if (danglingLine.getP0() == 0.0 && danglingLine.getQ0() == 0.0) { - LinkData.BranchAdmittanceMatrix adm = LinkData.calculateBranchAdmittance(danglingLine.getR(), danglingLine.getX(), 1.0, 0.0, 1.0, 0.0, new Complex(g1, b1), new Complex(g2, b2)); + LinkData.BranchAdmittanceMatrix adm = LinkData.calculateBranchAdmittance( + danglingLine.getR(), danglingLine.getX(), 1.0, 0.0, 1.0, 0.0, new Complex(danglingLine.getG(), danglingLine.getB()), new Complex(0, 0)); vBoundaryBus = adm.y21().multiply(v1).negate().divide(adm.y22()); } else { // Two buses Loadflow Complex sBoundary = new Complex(-danglingLine.getP0(), -danglingLine.getQ0()); - Complex ytr = new Complex(danglingLine.getR(), danglingLine.getX()).reciprocal(); - Complex ysh2 = new Complex(g2, b2); - Complex zt = ytr.add(ysh2).reciprocal(); - Complex v0 = ytr.multiply(v1).divide(ytr.add(ysh2)); - double v02 = v0.abs() * v0.abs(); + Complex zt = new Complex(danglingLine.getR(), danglingLine.getX()); + double v12 = v1.abs() * v1.abs(); - Complex sigma = zt.multiply(sBoundary.conjugate()).multiply(1.0 / v02); + Complex sigma = zt.multiply(sBoundary.conjugate()).multiply(1.0 / v12); double d = 0.25 + sigma.getReal() - sigma.getImaginary() * sigma.getImaginary(); // d < 0 Collapsed network if (d >= 0) { - vBoundaryBus = new Complex(0.5 + Math.sqrt(d), sigma.getImaginary()).multiply(v0); + vBoundaryBus = new Complex(0.5 + Math.sqrt(d), sigma.getImaginary()).multiply(v1); } } diff --git a/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/DanglingLineDataTest.java b/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/DanglingLineDataTest.java index ffc1fbe2378..41e8c504c8e 100644 --- a/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/DanglingLineDataTest.java +++ b/iidm/iidm-api/src/test/java/com/powsybl/iidm/network/util/DanglingLineDataTest.java @@ -27,7 +27,7 @@ void test() { DanglingLine danglingLine = new DanglingLineTestData().getDanglingLine(); DanglingLineData dlData = new DanglingLineData(danglingLine); - boolean ok = dlCompareBoundaryBusVoltage(dlData, 406.63382758266334, -8.573434828294932); + boolean ok = dlCompareBoundaryBusVoltage(dlData, 406.63378691, -8.57343339); assertTrue(ok); } @@ -39,7 +39,7 @@ void testP0Q0zero() { DanglingLine danglingLine = dlTestData.getDanglingLine(); DanglingLineData dlData = new DanglingLineData(danglingLine); - boolean ok = dlCompareBoundaryBusVoltage(dlData, 406.6200406620039, -8.60000143239463); + boolean ok = dlCompareBoundaryBusVoltage(dlData, 406.62, -8.60); assertTrue(ok); } diff --git a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java index d49383c47a2..b244718c921 100644 --- a/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java +++ b/iidm/iidm-impl/src/test/java/com/powsybl/iidm/network/impl/util/SVTest.java @@ -270,10 +270,10 @@ void testWithGeneration() { danglingLine.getTerminal().setQ(-7.413); danglingLine.getTerminal().getBusView().getBus().setAngle(0.0); danglingLine.getTerminal().getBusView().getBus().setV(100.0); - assertEquals(389.999, danglingLine.getBoundary().getP(), tol); - assertEquals(16.250, danglingLine.getBoundary().getQ(), tol); - assertEquals(130.037, danglingLine.getBoundary().getV(), tol); - assertEquals(0.995, danglingLine.getBoundary().getAngle(), tol); + assertEquals(389.953, danglingLine.getBoundary().getP(), tol); + assertEquals(16.314, danglingLine.getBoundary().getQ(), tol); + assertEquals(130.087, danglingLine.getBoundary().getV(), tol); + assertEquals(0.999, danglingLine.getBoundary().getAngle(), tol); } @Test diff --git a/iidm/iidm-serde/src/test/resources/V1_0/tieline.xml b/iidm/iidm-serde/src/test/resources/V1_0/tieline.xml index 4c154173bba..3d8e6261848 100644 --- a/iidm/iidm-serde/src/test/resources/V1_0/tieline.xml +++ b/iidm/iidm-serde/src/test/resources/V1_0/tieline.xml @@ -37,6 +37,6 @@ - - + + diff --git a/iidm/iidm-serde/src/test/resources/V1_0/tl-loading-limits.xml b/iidm/iidm-serde/src/test/resources/V1_0/tl-loading-limits.xml index d5c6641c1d1..99e2261bb82 100644 --- a/iidm/iidm-serde/src/test/resources/V1_0/tl-loading-limits.xml +++ b/iidm/iidm-serde/src/test/resources/V1_0/tl-loading-limits.xml @@ -37,7 +37,7 @@ - + @@ -47,5 +47,5 @@ - + diff --git a/iidm/iidm-serde/src/test/resources/V1_1/tieline.xml b/iidm/iidm-serde/src/test/resources/V1_1/tieline.xml index 624973a372b..0f57c945e39 100644 --- a/iidm/iidm-serde/src/test/resources/V1_1/tieline.xml +++ b/iidm/iidm-serde/src/test/resources/V1_1/tieline.xml @@ -37,6 +37,6 @@ - - + + diff --git a/iidm/iidm-serde/src/test/resources/V1_1/tl-loading-limits.xml b/iidm/iidm-serde/src/test/resources/V1_1/tl-loading-limits.xml index 6a17a7dedf9..b56a025060c 100644 --- a/iidm/iidm-serde/src/test/resources/V1_1/tl-loading-limits.xml +++ b/iidm/iidm-serde/src/test/resources/V1_1/tl-loading-limits.xml @@ -37,7 +37,7 @@ - + @@ -47,5 +47,5 @@ - + diff --git a/iidm/iidm-serde/src/test/resources/V1_2/tieline.xml b/iidm/iidm-serde/src/test/resources/V1_2/tieline.xml index cf2042dee9c..ca3888d70d0 100644 --- a/iidm/iidm-serde/src/test/resources/V1_2/tieline.xml +++ b/iidm/iidm-serde/src/test/resources/V1_2/tieline.xml @@ -37,6 +37,6 @@ - - + + diff --git a/iidm/iidm-serde/src/test/resources/V1_2/tl-loading-limits.xml b/iidm/iidm-serde/src/test/resources/V1_2/tl-loading-limits.xml index 4652595968e..b6503d727e8 100644 --- a/iidm/iidm-serde/src/test/resources/V1_2/tl-loading-limits.xml +++ b/iidm/iidm-serde/src/test/resources/V1_2/tl-loading-limits.xml @@ -37,7 +37,7 @@ - + @@ -47,5 +47,5 @@ - + diff --git a/iidm/iidm-serde/src/test/resources/V1_3/tieline.xml b/iidm/iidm-serde/src/test/resources/V1_3/tieline.xml index 55f04a4233d..9188c9d24ee 100644 --- a/iidm/iidm-serde/src/test/resources/V1_3/tieline.xml +++ b/iidm/iidm-serde/src/test/resources/V1_3/tieline.xml @@ -37,6 +37,6 @@ - - + + diff --git a/iidm/iidm-serde/src/test/resources/V1_3/tielineWithAliases.xml b/iidm/iidm-serde/src/test/resources/V1_3/tielineWithAliases.xml index 7a3a7c40090..ed3402f1df1 100644 --- a/iidm/iidm-serde/src/test/resources/V1_3/tielineWithAliases.xml +++ b/iidm/iidm-serde/src/test/resources/V1_3/tielineWithAliases.xml @@ -37,9 +37,9 @@ - + Alias Other alias - + diff --git a/iidm/iidm-serde/src/test/resources/V1_3/tl-loading-limits.xml b/iidm/iidm-serde/src/test/resources/V1_3/tl-loading-limits.xml index dc3262f2191..6e99285e978 100644 --- a/iidm/iidm-serde/src/test/resources/V1_3/tl-loading-limits.xml +++ b/iidm/iidm-serde/src/test/resources/V1_3/tl-loading-limits.xml @@ -37,7 +37,7 @@ - + @@ -47,5 +47,5 @@ - + diff --git a/iidm/iidm-serde/src/test/resources/V1_4/tieline.xml b/iidm/iidm-serde/src/test/resources/V1_4/tieline.xml index a82da035672..73088c53ed5 100644 --- a/iidm/iidm-serde/src/test/resources/V1_4/tieline.xml +++ b/iidm/iidm-serde/src/test/resources/V1_4/tieline.xml @@ -37,6 +37,6 @@ - - + + diff --git a/iidm/iidm-serde/src/test/resources/V1_4/tielineWithAliases.xml b/iidm/iidm-serde/src/test/resources/V1_4/tielineWithAliases.xml index dd65ec95d8c..25d9fd7c9b7 100644 --- a/iidm/iidm-serde/src/test/resources/V1_4/tielineWithAliases.xml +++ b/iidm/iidm-serde/src/test/resources/V1_4/tielineWithAliases.xml @@ -37,9 +37,9 @@ - + Alias Other alias - + diff --git a/iidm/iidm-serde/src/test/resources/V1_4/tl-loading-limits.xml b/iidm/iidm-serde/src/test/resources/V1_4/tl-loading-limits.xml index fa6fad52325..397b0bf9249 100644 --- a/iidm/iidm-serde/src/test/resources/V1_4/tl-loading-limits.xml +++ b/iidm/iidm-serde/src/test/resources/V1_4/tl-loading-limits.xml @@ -37,7 +37,7 @@ - + @@ -47,5 +47,5 @@ - + diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractAreaTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractAreaTest.java index fbeb8424364..e8fc08d56dc 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractAreaTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractAreaTest.java @@ -187,8 +187,8 @@ public void testGetAreaBoundary() { assertNotNull(areaBoundary); assertTrue(areaBoundary.isAc()); assertEquals(controlAreaA.getId(), areaBoundary.getArea().getId()); - assertEquals(-301.47, areaBoundary.getP(), DELTA); - assertEquals(-116.52, areaBoundary.getQ(), DELTA); + assertEquals(-301.44, areaBoundary.getP(), DELTA); + assertEquals(-116.55, areaBoundary.getQ(), DELTA); controlAreaA.removeAreaBoundary(dlXnode1A.getBoundary()); assertNull(controlAreaA.getAreaBoundary(dlXnode1A.getBoundary())); @@ -196,13 +196,13 @@ public void testGetAreaBoundary() { @Test public void areaInterchangeComputation() { - assertEquals(-602.94, controlAreaA.getAcInterchange(), DELTA); + assertEquals(-602.88, controlAreaA.getAcInterchange(), DELTA); assertEquals(0.0, controlAreaA.getDcInterchange()); - assertEquals(-602.94, controlAreaA.getInterchange(), DELTA); + assertEquals(-602.88, controlAreaA.getInterchange(), DELTA); - assertEquals(+602.94, controlAreaB.getAcInterchange(), DELTA); + assertEquals(+602.88, controlAreaB.getAcInterchange(), DELTA); assertEquals(0.0, controlAreaB.getDcInterchange()); - assertEquals(+602.94, controlAreaB.getInterchange(), DELTA); + assertEquals(+602.88, controlAreaB.getInterchange(), DELTA); // no boundaries defined assertEquals(0.0, regionAB.getAcInterchange()); @@ -211,9 +211,9 @@ public void areaInterchangeComputation() { // verify NaN do not mess up the calculation dlXnode1A.getTerminal().setP(Double.NaN); - assertEquals(-301.47, controlAreaA.getAcInterchange(), DELTA); + assertEquals(-301.44, controlAreaA.getAcInterchange(), DELTA); assertEquals(0.0, controlAreaA.getDcInterchange()); - assertEquals(-301.47, controlAreaA.getInterchange(), DELTA); + assertEquals(-301.44, controlAreaA.getInterchange(), DELTA); } @Test @@ -315,9 +315,9 @@ public void testAddSameBoundary() { .newAreaBoundary().setBoundary(dlXnode2A.getBoundary()).setAc(true).add(); // no change assertEquals(2, controlAreaA.getAreaBoundaryStream().count()); - assertEquals(-602.94, controlAreaA.getAcInterchange(), DELTA); + assertEquals(-602.88, controlAreaA.getAcInterchange(), DELTA); assertEquals(0.0, controlAreaA.getDcInterchange()); - assertEquals(-602.94, controlAreaA.getInterchange(), DELTA); + assertEquals(-602.88, controlAreaA.getInterchange(), DELTA); // change them to DC controlAreaA @@ -325,8 +325,8 @@ public void testAddSameBoundary() { .newAreaBoundary().setBoundary(dlXnode2A.getBoundary()).setAc(false).add(); assertEquals(2, controlAreaA.getAreaBoundaryStream().count()); assertEquals(0.0, controlAreaA.getAcInterchange()); - assertEquals(-602.94, controlAreaA.getDcInterchange(), DELTA); - assertEquals(-602.94, controlAreaA.getInterchange(), DELTA); + assertEquals(-602.88, controlAreaA.getDcInterchange(), DELTA); + assertEquals(-602.88, controlAreaA.getInterchange(), DELTA); } @Test diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractMergeNetworkTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractMergeNetworkTest.java index 91e732fbacd..480f52415f8 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractMergeNetworkTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractMergeNetworkTest.java @@ -127,10 +127,10 @@ public void testMergeAndDetach() { DanglingLine dl1 = detachedN1.getDanglingLine("dl1"); DanglingLine dl2 = merge.getDanglingLine("dl2"); // - P0 and Q0 of the removed tie line's underlying dangling lines were updated: - assertEquals(-1724.437, dl1.getP0(), 0.001); - assertEquals(1605.281, dl1.getQ0(), 0.001); - assertEquals(-1724.437, dl2.getP0(), 0.001); - assertEquals(1605.281, dl2.getQ0(), 0.001); + assertEquals(-731.312, dl1.getP0(), 0.001); + assertEquals(-1254.625, dl1.getQ0(), 0.001); + assertEquals(-731.312, dl2.getP0(), 0.001); + assertEquals(-1254.625, dl2.getQ0(), 0.001); // detach(n2) assertTrue(subnetwork2.isDetachable()); diff --git a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTieLineTest.java b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTieLineTest.java index fc3055009ee..cd0abf8c776 100644 --- a/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTieLineTest.java +++ b/iidm/iidm-tck/src/test/java/com/powsybl/iidm/network/tck/AbstractTieLineTest.java @@ -155,7 +155,8 @@ public void testTieLineAdder() { verifyNoMoreInteractions(mockedListener); // Reuse adder - ValidationException e = assertThrows(ValidationException.class, () -> adder.setId("testTie2").add()); + adder.setId("testTie2"); + ValidationException e = assertThrows(ValidationException.class, adder::add); assertTrue(e.getMessage().contains("already has a tie line")); // Update power flows, voltages and angles @@ -172,15 +173,15 @@ public void testTieLineAdder() { // test boundaries values SV expectedSV1 = new SV(p1, q1, v1, angle1, TwoSides.ONE); - SV expectedSV2 = new SV(p2, q2, v2, angle2, TwoSides.TWO); - assertEquals(expectedSV1.otherSideP(danglingLine1, true), danglingLine1.getBoundary().getP(), 0.0d); - assertEquals(expectedSV1.otherSideQ(danglingLine1, true), danglingLine1.getBoundary().getQ(), 0.0d); - assertEquals(expectedSV2.otherSideP(danglingLine2, true), danglingLine2.getBoundary().getP(), 0.0d); - assertEquals(expectedSV2.otherSideQ(danglingLine2, true), danglingLine2.getBoundary().getQ(), 0.0d); - assertEquals(expectedSV1.otherSideU(danglingLine1, true), danglingLine1.getBoundary().getV(), 0.0d); - assertEquals(expectedSV1.otherSideA(danglingLine1, true), danglingLine1.getBoundary().getAngle(), 0.0d); - assertEquals(expectedSV2.otherSideU(danglingLine2, true), danglingLine2.getBoundary().getV(), 0.0d); - assertEquals(expectedSV2.otherSideA(danglingLine2, true), danglingLine2.getBoundary().getAngle(), 0.0d); + SV expectedSV2 = new SV(p2, q2, v2, angle2, TwoSides.ONE); + assertEquals(expectedSV1.otherSideP(danglingLine1, false), danglingLine1.getBoundary().getP(), 0.0d); + assertEquals(expectedSV1.otherSideQ(danglingLine1, false), danglingLine1.getBoundary().getQ(), 0.0d); + assertEquals(expectedSV2.otherSideP(danglingLine2, false), danglingLine2.getBoundary().getP(), 0.0d); + assertEquals(expectedSV2.otherSideQ(danglingLine2, false), danglingLine2.getBoundary().getQ(), 0.0d); + assertEquals(expectedSV1.otherSideU(danglingLine1, false), danglingLine1.getBoundary().getV(), 0.0d); + assertEquals(expectedSV1.otherSideA(danglingLine1, false), danglingLine1.getBoundary().getAngle(), 0.0d); + assertEquals(expectedSV2.otherSideU(danglingLine2, false), danglingLine2.getBoundary().getV(), 0.0d); + assertEquals(expectedSV2.otherSideA(danglingLine2, false), danglingLine2.getBoundary().getAngle(), 0.0d); // test paired dangling line retrieval - For paired dangling lines Optional otherSide1 = TieLineUtil.getPairedDanglingLine(danglingLine1); @@ -198,10 +199,10 @@ public void testTieLineAdder() { @Test public void danglingLine1NotSet() { // adder - ValidationException e = assertThrows(ValidationException.class, () -> network.newTieLine() + TieLineAdder tieLineAdder = network.newTieLine() .setId("testTie") - .setName("testNameTie") - .add()); + .setName("testNameTie"); + ValidationException e = assertThrows(ValidationException.class, tieLineAdder::add); assertTrue(e.getMessage().contains("undefined dangling line")); } @@ -221,11 +222,11 @@ public void danglingLine2NotSet() { .setPairingKey("ucte") .add(); // adder - ValidationException e = assertThrows(ValidationException.class, () -> network.newTieLine() + TieLineAdder tieLineAdder = network.newTieLine() .setId("testTie") .setName("testNameTie") - .setDanglingLine1(dl1.getId()) - .add()); + .setDanglingLine1(dl1.getId()); + ValidationException e = assertThrows(ValidationException.class, tieLineAdder::add); assertTrue(e.getMessage().contains("undefined dangling line")); } @@ -309,14 +310,14 @@ public void testRemoveUpdateDanglingLines() { assertEquals(0.0, line2.getDanglingLine2().getQ0()); line1.remove(true); line2.remove(true); - assertEquals(301.316, line1.getDanglingLine1().getP0(), 0.001); - assertEquals(116.525, line1.getDanglingLine1().getQ0(), 0.001); - assertEquals(-301.782, line1.getDanglingLine2().getP0(), 0.001); - assertEquals(-116.442, line1.getDanglingLine2().getQ0(), 0.001); - assertEquals(301.316, line2.getDanglingLine1().getP0(), 0.001); - assertEquals(116.525, line2.getDanglingLine1().getQ0(), 0.001); - assertEquals(-301.782, line2.getDanglingLine2().getP0(), 0.001); - assertEquals(-116.442, line2.getDanglingLine2().getQ0(), 0.001); + assertEquals(301.278, line1.getDanglingLine1().getP0(), 0.001); + assertEquals(116.563, line1.getDanglingLine1().getQ0(), 0.001); + assertEquals(-301.745, line1.getDanglingLine2().getP0(), 0.001); + assertEquals(-116.566, line1.getDanglingLine2().getQ0(), 0.001); + assertEquals(301.278, line2.getDanglingLine1().getP0(), 0.001); + assertEquals(116.563, line2.getDanglingLine1().getQ0(), 0.001); + assertEquals(-301.745, line2.getDanglingLine2().getP0(), 0.001); + assertEquals(-116.567, line2.getDanglingLine2().getQ0(), 0.001); } @Test diff --git a/matpower/matpower-converter/src/test/resources/be.json b/matpower/matpower-converter/src/test/resources/be.json index 3acebf251a2..11a0e62d8e3 100644 --- a/matpower/matpower-converter/src/test/resources/be.json +++ b/matpower/matpower-converter/src/test/resources/be.json @@ -101,8 +101,8 @@ "shuntConductance" : 0.0, "shuntSusceptance" : 0.0, "areaNumber" : 1, - "voltageMagnitude" : 1.0022079161750066, - "voltageAngle" : -5.5802145734187345, + "voltageMagnitude" : 0.9993638246917638, + "voltageAngle" : -5.508577312691308, "baseVoltage" : 225.0, "lossZone" : 1, "maximumVoltageMagnitude" : 0.0, @@ -116,8 +116,8 @@ "shuntConductance" : 0.0, "shuntSusceptance" : 0.0, "areaNumber" : 1, - "voltageMagnitude" : 1.0819778377115963, - "voltageAngle" : -6.588106220321681, + "voltageMagnitude" : 1.0810323300546612, + "voltageAngle" : -6.5628934078599075, "baseVoltage" : 380.0, "lossZone" : 1, "maximumVoltageMagnitude" : 0.0, @@ -131,8 +131,8 @@ "shuntConductance" : 0.0, "shuntSusceptance" : 0.0, "areaNumber" : 1, - "voltageMagnitude" : 1.0812675043886917, - "voltageAngle" : -6.576440343113285, + "voltageMagnitude" : 1.0810506038881742, + "voltageAngle" : -6.568066330700518, "baseVoltage" : 380.0, "lossZone" : 1, "maximumVoltageMagnitude" : 0.0, @@ -146,8 +146,8 @@ "shuntConductance" : 0.0, "shuntSusceptance" : 0.0, "areaNumber" : 1, - "voltageMagnitude" : 0.9998493163932333, - "voltageAngle" : -5.638538004235438, + "voltageMagnitude" : 0.999160561117725, + "voltageAngle" : -5.520447327315214, "baseVoltage" : 225.0, "lossZone" : 1, "maximumVoltageMagnitude" : 0.0, @@ -161,8 +161,8 @@ "shuntConductance" : 0.0, "shuntSusceptance" : 0.0, "areaNumber" : 1, - "voltageMagnitude" : 1.0858193263940736, - "voltageAngle" : -6.7469452159556695, + "voltageMagnitude" : 1.0857971311504089, + "voltageAngle" : -6.744481665758925, "baseVoltage" : 380.0, "lossZone" : 1, "maximumVoltageMagnitude" : 0.0, From 9b4816015da1b89bfd187b5070000536617ad7a8 Mon Sep 17 00:00:00 2001 From: Sophie Frasnedo <93923177+So-Fras@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:56:58 +0200 Subject: [PATCH 2/3] Update documentation regarding substations. (#3172) * Remove wrong sentence in documentation * Fix documentation following comment Signed-off-by: Sophie Frasnedo Co-authored-by: Olivier Perrin --- docs/grid_model/index.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/grid_model/index.md b/docs/grid_model/index.md index c8dccbba5d6..56f3b648180 100644 --- a/docs/grid_model/index.md +++ b/docs/grid_model/index.md @@ -10,7 +10,9 @@ going_further.md Powsybl features are strongly based on an internal grid model initially developed under the iTesla project, a research project funded by the [European Union 7th Framework programme](https://cordis.europa.eu/project/id/283012) (FP7). The grid model is known as `iidm` (iTesla Internal Data Model). One of the iTesla outputs was a toolbox designed to support the decision-making process of power system operation from two-days ahead to real time. The `iidm` grid model was at the center of the toolbox. -To build an electrical network model, the substations must be defined first. The equipment of a substation (busbar sections, switches, buses, loads, generators, shunt compensators, static VAR compensators, HVDC converters stations, etc.) is grouped in voltage levels. Transformers present in a substation connect its different voltage levels. Transmission lines (AC and DC) connect the substations. +The equipment of a substation (busbar sections, switches, buses, loads, generators, shunt compensators, static VAR compensators, HVDC converters stations, etc.) is grouped in voltage levels. Transformers present in a substation connect its different voltage levels. Transmission lines (AC and DC) connect the substations. + +To build an electrical network model, the common way is to define the substations first, then to define their voltage levels. But for some specific cases, it is also possible to create voltage levels without a substation. The grid model allows a full representation of the substation connectivity where all the switching devices and busbar sections are defined, this topology is called node/breaker view. Automated topology calculation allows for the calculation of the network bus/breaker view as well as the network bus view. From cdee51a924668e5c19eeed53abd87d6b3b23d647 Mon Sep 17 00:00:00 2001 From: Olivier Perrin Date: Fri, 11 Oct 2024 10:24:29 +0200 Subject: [PATCH 3/3] Return stable ids/nodes in ViolationLocation classes (#3178) * Return configured bus ids instead of merged bus ids in BusBreakerViolationLocation * Change unit test to check that all configured buses of the merged bus are indeed returned * Store the nodes instead of the ids of the busbar sections in node/breaker topology * Don't create a ViolationLocation if no LimitViolation is detected * API extension * Sonar fix Signed-off-by: Olivier Perrin Co-authored-by: Didier Vidal --- .../security/BusBreakerViolationLocation.java | 53 +++++++++------ .../security/LimitViolationDetection.java | 28 +++++--- .../NodeBreakerViolationLocation.java | 42 +++++++++--- .../powsybl/security/ViolationLocation.java | 20 +++--- .../DefaultLimitViolationDetector.java | 5 +- .../json/ViolationLocationDeserializer.java | 21 +++--- .../json/ViolationLocationSerializer.java | 17 ++--- .../security/LimitViolationDetectionTest.java | 67 +++++++++++++++---- .../powsybl/security/LimitViolationTest.java | 4 +- .../security/converter/ExporterTest.java | 4 +- .../json/PostContingencyResultTest.java | 4 +- .../resources/PostContingencyResultTest.json | 5 +- .../resources/SecurityAnalysisResult.json | 5 +- 13 files changed, 174 insertions(+), 101 deletions(-) diff --git a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/BusBreakerViolationLocation.java b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/BusBreakerViolationLocation.java index 932a7cb08cf..a06faf79262 100644 --- a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/BusBreakerViolationLocation.java +++ b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/BusBreakerViolationLocation.java @@ -7,47 +7,58 @@ */ package com.powsybl.security; +import com.powsybl.iidm.network.Network; + +import java.util.List; import java.util.Objects; -import java.util.Optional; /** * @author Étienne Lesot {@literal } */ public class BusBreakerViolationLocation implements ViolationLocation { - private final String voltageLevelId; - private final String busId; + private final List busIds; - public BusBreakerViolationLocation(String voltageLevelId, String busId) { - Objects.requireNonNull(voltageLevelId, "voltageLevelId"); - this.voltageLevelId = voltageLevelId; - this.busId = busId; + /** + * Create a ViolationLocation for a violation detected in a voltage level in bus/breaker topology. + * @param busIds The ids of the configured buses (of the bus/breaker view) where the violation was detected. + */ + public BusBreakerViolationLocation(List busIds) { + this.busIds = Objects.requireNonNull(busIds, "busIds should not be null."); } - @Override - public String getVoltageLevelId() { - return voltageLevelId; + /** + * Get the ids of the configured buses (of the bus/breaker view) where the violation was detected. + * @return the configured bus ids + */ + public List getBusIds() { + return busIds; } @Override - public Optional getBusId() { - return Optional.ofNullable(busId); + public Type getType() { + return Type.BUS_BREAKER; } @Override - public String getId() { - return busId == null ? voltageLevelId : busId; + public String toString() { + return "BusBreakerViolationLocation{" + + "busIds='" + busIds + '\'' + + '}'; } @Override - public Type getType() { - return Type.BUS_BREAKER; + public BusView getBusView(Network network) { + return () -> busIds.stream() + .map(id -> network.getBusBreakerView().getBus(id)) + .filter(b -> b.getConnectedTerminalCount() > 0) + .map(b -> b.getConnectedTerminals().iterator().next().getBusView().getBus()) + .distinct(); + } @Override - public String toString() { - return "BusBreakerViolationLocation{" + - "voltageLevelId='" + voltageLevelId + '\'' + - ", busId='" + busId + '\'' + - '}'; + public BusView getBusBreakerView(Network network) { + return () -> busIds.stream().map(id -> network.getBusBreakerView().getBus(id)); } + } diff --git a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/LimitViolationDetection.java b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/LimitViolationDetection.java index 17e389b88f6..44e8ee1c41e 100644 --- a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/LimitViolationDetection.java +++ b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/LimitViolationDetection.java @@ -11,8 +11,11 @@ import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.limitmodification.LimitsComputer; import com.powsybl.iidm.network.util.LimitViolationUtils; +import com.powsybl.iidm.network.util.Networks; import com.powsybl.iidm.network.util.PermanentLimitCheckResult; import com.powsybl.security.detectors.LoadingLimitType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; import java.util.Set; @@ -23,6 +26,8 @@ */ public final class LimitViolationDetection { + private static final Logger LOGGER = LoggerFactory.getLogger(LimitViolationDetection.class); + private LimitViolationDetection() { } @@ -206,15 +211,14 @@ private static void checkVoltage(Bus bus, Consumer consumer) { static void checkVoltage(Bus bus, double value, Consumer consumer) { VoltageLevel vl = bus.getVoltageLevel(); - ViolationLocation voltageViolationLocation = createViolationLocation(bus); if (!Double.isNaN(vl.getLowVoltageLimit()) && value <= vl.getLowVoltageLimit()) { consumer.accept(new LimitViolation(vl.getId(), vl.getOptionalName().orElse(null), LimitViolationType.LOW_VOLTAGE, - vl.getLowVoltageLimit(), 1., value, voltageViolationLocation)); + vl.getLowVoltageLimit(), 1., value, createViolationLocation(bus))); } if (!Double.isNaN(vl.getHighVoltageLimit()) && value >= vl.getHighVoltageLimit()) { consumer.accept(new LimitViolation(vl.getId(), vl.getOptionalName().orElse(null), LimitViolationType.HIGH_VOLTAGE, - vl.getHighVoltageLimit(), 1., value, voltageViolationLocation)); + vl.getHighVoltageLimit(), 1., value, createViolationLocation(bus))); } } @@ -268,14 +272,18 @@ static void checkVoltageAngle(VoltageAngleLimit voltageAngleLimit, double value, public static ViolationLocation createViolationLocation(Bus bus) { VoltageLevel vl = bus.getVoltageLevel(); if (vl.getTopologyKind() == TopologyKind.NODE_BREAKER) { - List busbarIds = bus.getConnectedTerminalStream() - .map(Terminal::getConnectable) - .filter(BusbarSection.class::isInstance) - .map(Connectable::getId) - .toList(); - return new NodeBreakerViolationLocation(vl.getId(), busbarIds); + List nodes = Networks.getNodesByBus(vl).get(bus.getId()).stream().toList(); + return new NodeBreakerViolationLocation(vl.getId(), nodes); } else { - return new BusBreakerViolationLocation(vl.getId(), bus.getId()); + try { + List configuredBusIds = vl.getBusBreakerView().getBusStreamFromBusViewBusId(bus.getId()) + .map(Identifiable::getId) + .sorted().toList(); + return new BusBreakerViolationLocation(configuredBusIds); + } catch (Exception e) { + LOGGER.error("Error generating ViolationLocation", e); + return null; + } } } } diff --git a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/NodeBreakerViolationLocation.java b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/NodeBreakerViolationLocation.java index 19386247bdb..2610832fe76 100644 --- a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/NodeBreakerViolationLocation.java +++ b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/NodeBreakerViolationLocation.java @@ -7,6 +7,10 @@ */ package com.powsybl.security; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.iidm.network.util.Networks; + import java.util.List; import java.util.Objects; @@ -15,27 +19,43 @@ */ public class NodeBreakerViolationLocation implements ViolationLocation { private final String voltageLevelId; - private final List busBarIds; + private final List nodes; - public NodeBreakerViolationLocation(String voltageLevelId, List busBarIds) { - Objects.requireNonNull(voltageLevelId, "voltageLevelId"); - this.voltageLevelId = voltageLevelId; - this.busBarIds = busBarIds; + /** + * Create a ViolationLocation for a violation detected in a voltage level in node/breaker topology. + * @param voltageLevelId the id of the voltage level + * @param nodes The list of the nodes where the violation was detected. + */ + public NodeBreakerViolationLocation(String voltageLevelId, List nodes) { + this.voltageLevelId = Objects.requireNonNull(voltageLevelId, "voltageLevelId should not be null"); + this.nodes = Objects.requireNonNull(nodes, "nodes should not be null"); } - @Override public String getVoltageLevelId() { return voltageLevelId; } + public List getNodes() { + return nodes; + } + @Override - public List getBusBarIds() { - return busBarIds; + public BusView getBusView(Network network) { + return () -> { + VoltageLevel vl = network.getVoltageLevel(voltageLevelId); + var busView = vl.getBusView(); + return Networks.getNodesByBus(vl) + .entrySet() + .stream() + .filter(e -> nodes.stream().anyMatch(i -> e.getValue().contains(i))) + .map(e -> busView.getBus(e.getKey())) + .distinct(); + }; } @Override - public String getId() { - return busBarIds.isEmpty() ? voltageLevelId : busBarIds.get(0); + public BusView getBusBreakerView(Network network) { + throw new UnsupportedOperationException(); } @Override @@ -47,7 +67,7 @@ public Type getType() { public String toString() { return "NodeBreakerVoltageLocation{" + "voltageLevelId='" + voltageLevelId + '\'' + - ", busBarIds=" + busBarIds + + ", nodes=" + nodes + '}'; } } diff --git a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/ViolationLocation.java b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/ViolationLocation.java index 54e6e5f0798..c5f67caa37a 100644 --- a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/ViolationLocation.java +++ b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/ViolationLocation.java @@ -7,9 +7,10 @@ */ package com.powsybl.security; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Network; + +import java.util.stream.Stream; /** * @author Étienne Lesot {@literal } @@ -21,18 +22,13 @@ enum Type { BUS_BREAKER } - String getId(); - Type getType(); - String getVoltageLevelId(); + BusView getBusView(Network network); - default Optional getBusId() { - return Optional.empty(); - } + BusView getBusBreakerView(Network network); - default List getBusBarIds() { - return Collections.emptyList(); + interface BusView { + Stream getBusStream(); } - } diff --git a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/detectors/DefaultLimitViolationDetector.java b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/detectors/DefaultLimitViolationDetector.java index 3f6e3ac3609..94b771ab58c 100644 --- a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/detectors/DefaultLimitViolationDetector.java +++ b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/detectors/DefaultLimitViolationDetector.java @@ -78,15 +78,14 @@ public void checkApparentPower(ThreeWindingsTransformer transformer, ThreeSides @Override public void checkVoltage(Bus bus, double value, Consumer consumer) { VoltageLevel vl = bus.getVoltageLevel(); - ViolationLocation voltageViolationLocation = createViolationLocation(bus); if (!Double.isNaN(vl.getLowVoltageLimit()) && value <= vl.getLowVoltageLimit()) { consumer.accept(new LimitViolation(vl.getId(), vl.getOptionalName().orElse(null), LimitViolationType.LOW_VOLTAGE, - vl.getLowVoltageLimit(), limitReductionValue, value, voltageViolationLocation)); + vl.getLowVoltageLimit(), limitReductionValue, value, createViolationLocation(bus))); } if (!Double.isNaN(vl.getHighVoltageLimit()) && value >= vl.getHighVoltageLimit()) { consumer.accept(new LimitViolation(vl.getId(), vl.getOptionalName().orElse(null), LimitViolationType.HIGH_VOLTAGE, - vl.getHighVoltageLimit(), limitReductionValue, value, voltageViolationLocation)); + vl.getHighVoltageLimit(), limitReductionValue, value, createViolationLocation(bus))); } } diff --git a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/json/ViolationLocationDeserializer.java b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/json/ViolationLocationDeserializer.java index 6cb5152dee3..84926326662 100644 --- a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/json/ViolationLocationDeserializer.java +++ b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/json/ViolationLocationDeserializer.java @@ -25,9 +25,9 @@ */ public class ViolationLocationDeserializer extends StdDeserializer { - private static final String BUS_ID = "busId"; + private static final String BUS_IDS = "busIds"; private static final String VOLTAGE_LEVEL_ID = "voltageLevelId"; - private static final String BUS_BAR_IDS = "busbarIds"; + private static final String NODES = "nodes"; public ViolationLocationDeserializer() { super(ViolationLocation.class); @@ -36,8 +36,8 @@ public ViolationLocationDeserializer() { @Override public ViolationLocation deserialize(JsonParser parser, DeserializationContext deserializationContext) throws IOException { String voltageLevelId = null; - String busId = null; - List busbarIds = new ArrayList<>(); + List busIds = new ArrayList<>(); + List nodes = new ArrayList<>(); ViolationLocation.Type type = null; while (parser.nextToken() != JsonToken.END_OBJECT) { switch (parser.currentName()) { @@ -45,26 +45,27 @@ public ViolationLocation deserialize(JsonParser parser, DeserializationContext d parser.nextToken(); type = JsonUtil.readValue(deserializationContext, parser, ViolationLocation.Type.class); break; - case BUS_ID: - busId = parser.nextTextValue(); + case BUS_IDS: + parser.nextToken(); + busIds = JsonUtil.readList(deserializationContext, parser, String.class); break; case VOLTAGE_LEVEL_ID: voltageLevelId = parser.nextTextValue(); break; - case BUS_BAR_IDS: + case NODES: parser.nextToken(); - busbarIds = JsonUtil.readList(deserializationContext, parser, String.class); + nodes = JsonUtil.readList(deserializationContext, parser, Integer.class); break; default: throw new IllegalStateException("Unexpected field: " + parser.currentName()); } } if (type == ViolationLocation.Type.NODE_BREAKER) { - return new NodeBreakerViolationLocation(voltageLevelId, busbarIds); + return new NodeBreakerViolationLocation(voltageLevelId, nodes); } else if (type == ViolationLocation.Type.BUS_BREAKER) { - return new BusBreakerViolationLocation(voltageLevelId, busId); + return new BusBreakerViolationLocation(busIds); } else { throw new IllegalStateException("type should be among [NODE_BREAKER, BUS_BREAKER]."); } diff --git a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/json/ViolationLocationSerializer.java b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/json/ViolationLocationSerializer.java index 219986b72ee..9d8a7e4b6de 100644 --- a/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/json/ViolationLocationSerializer.java +++ b/security-analysis/security-analysis-api/src/main/java/com/powsybl/security/json/ViolationLocationSerializer.java @@ -11,10 +11,11 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.powsybl.commons.json.JsonUtil; +import com.powsybl.security.BusBreakerViolationLocation; +import com.powsybl.security.NodeBreakerViolationLocation; import com.powsybl.security.ViolationLocation; import java.io.IOException; -import java.util.Optional; /** * @author Etienne Lesot {@literal } @@ -29,13 +30,13 @@ public ViolationLocationSerializer() { public void serialize(ViolationLocation violationLocation, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeStartObject(); JsonUtil.writeOptionalEnumField(jsonGenerator, "type", violationLocation.getType()); - jsonGenerator.writeStringField("voltageLevelId", violationLocation.getVoltageLevelId()); - Optional busId = violationLocation.getBusId(); - if (busId.isPresent()) { - jsonGenerator.writeStringField("busId", busId.get()); - } - if (!violationLocation.getBusBarIds().isEmpty()) { - serializerProvider.defaultSerializeField("busbarIds", violationLocation.getBusBarIds(), jsonGenerator); + if (ViolationLocation.Type.NODE_BREAKER == violationLocation.getType()) { + NodeBreakerViolationLocation location = (NodeBreakerViolationLocation) violationLocation; + jsonGenerator.writeStringField("voltageLevelId", location.getVoltageLevelId()); + serializerProvider.defaultSerializeField("nodes", location.getNodes(), jsonGenerator); + } else { + BusBreakerViolationLocation location = (BusBreakerViolationLocation) violationLocation; + serializerProvider.defaultSerializeField("busIds", location.getBusIds(), jsonGenerator); } jsonGenerator.writeEndObject(); } diff --git a/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/LimitViolationDetectionTest.java b/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/LimitViolationDetectionTest.java index 8894f659205..cd203e7c214 100644 --- a/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/LimitViolationDetectionTest.java +++ b/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/LimitViolationDetectionTest.java @@ -9,8 +9,8 @@ import com.powsybl.contingency.ContingencyContext; import com.powsybl.iidm.criteria.NetworkElementIdListCriterion; -import com.powsybl.iidm.criteria.duration.PermanentDurationCriterion; import com.powsybl.iidm.criteria.duration.EqualityTemporaryDurationCriterion; +import com.powsybl.iidm.criteria.duration.PermanentDurationCriterion; import com.powsybl.iidm.network.*; import com.powsybl.iidm.network.limitmodification.LimitsComputer; import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; @@ -26,8 +26,10 @@ import java.util.*; import java.util.function.Consumer; +import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -104,7 +106,8 @@ protected void checkVoltageAngle(VoltageAngleLimit voltageAngleLimit, double vol @Test void testVoltageViolationDetection() { Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); - LimitViolationDetection.checkVoltage((Bus) network.getIdentifiable("NHV2"), 620, violationsCollector::add); + Bus mergedBus = getMergedBusFromConfiguredBusId(network, "NHV2"); + LimitViolationDetection.checkVoltage(mergedBus, 620, violationsCollector::add); Assertions.assertThat(violationsCollector) .hasSize(1) .allSatisfy(l -> { @@ -118,7 +121,19 @@ void testVoltageViolationDetection() { @Test void testVoltageViolationDetectionWithDetailLimitViolationId() { Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits(); - LimitViolationDetection.checkVoltage((Bus) network.getIdentifiable("NHV2"), 620, violationsCollector::add); + // Add a new bus "NHV20" which will be merged with "NHV2" in the bs view + VoltageLevel vlh2 = network.getVoltageLevel("VLHV2"); + vlh2.getBusBreakerView().newBus() + .setId("NHV20") + .add(); + vlh2.getBusBreakerView().newSwitch() + .setBus1("NHV2").setBus2("NHV20") + .setOpen(false).setId("DJ") + .add(); + // Retrieve the merged bus containing "NHV2" + Bus mergedBus = getMergedBusFromConfiguredBusId(network, "NHV2"); + // Check the voltage on this merged bus + LimitViolationDetection.checkVoltage(mergedBus, 620, violationsCollector::add); Assertions.assertThat(violationsCollector) .hasSize(1) .allSatisfy(l -> { @@ -130,15 +145,24 @@ void testVoltageViolationDetectionWithDetailLimitViolationId() { .get() .isInstanceOfSatisfying(BusBreakerViolationLocation.class, vli -> { - assertEquals("NHV2", vli.getId()); - assertTrue(vli.getBusId().isPresent()); - assertEquals("NHV2", vli.getBusId().get()); - assertEquals("VLHV2", vli.getVoltageLevelId()); - Assertions.assertThat(vli.getBusBarIds()).isEmpty(); + assertEquals(ViolationLocation.Type.BUS_BREAKER, vli.getType()); + assertEquals(2, vli.getBusIds().size()); + assertTrue(vli.getBusIds().containsAll(List.of("NHV2", "NHV20"))); // Both configured buses are present + Set buses = vli.getBusBreakerView(network).getBusStream().collect(Collectors.toSet()); + assertEquals(2, buses.size()); + assertTrue(buses.contains(network.getBusBreakerView().getBus("NHV2"))); + assertTrue(buses.contains(network.getBusBreakerView().getBus("NHV20"))); + Set mergedBuses = vli.getBusView(network).getBusStream().collect(Collectors.toSet()); + assertEquals(1, mergedBuses.size()); }); }); } + private Bus getMergedBusFromConfiguredBusId(Network network, String busId) { + Bus b = (Bus) network.getIdentifiable(busId); + return b.getVoltageLevel().getBusView().getMergedBus(busId); + } + @Test void testVoltageViolationDetectionWithBusBarIds() { Network network = FourSubstationsNodeBreakerFactory.create(); @@ -155,15 +179,30 @@ void testVoltageViolationDetectionWithBusBarIds() { .get() .isInstanceOfSatisfying(NodeBreakerViolationLocation.class, vli -> { - assertEquals("S1VL1_BBS", vli.getId()); - assertTrue(vli.getBusId().isEmpty()); + assertEquals(ViolationLocation.Type.NODE_BREAKER, vli.getType()); assertEquals("S1VL1", vli.getVoltageLevelId()); - Assertions.assertThat(vli.getBusBarIds()) - .hasSize(1) - .first() - .isEqualTo("S1VL1_BBS"); + Assertions.assertThat(vli.getNodes()) + .hasSize(5) + .isEqualTo(List.of(0, 1, 2, 3, 4)); + assertThrows(UnsupportedOperationException.class, () -> vli.getBusBreakerView(network)); + Set mergedBuses = vli.getBusView(network).getBusStream().collect(Collectors.toSet()); + assertEquals(1, mergedBuses.size()); + assertTrue(mergedBuses.contains(network.getBusView().getBus("S1VL1_0"))); }); }); + // Check corresponding busbar sections + ViolationLocation violationLocation = violationsCollector.get(0).getViolationLocation().orElseThrow(); + NodeBreakerViolationLocation vloc = (NodeBreakerViolationLocation) violationLocation; + VoltageLevel vl = network.getVoltageLevel(vloc.getVoltageLevelId()); + List busbarSectionIds = vloc.getNodes().stream() + .map(node -> vl.getNodeBreakerView().getTerminal(node)) + .filter(Objects::nonNull) + .map(Terminal::getConnectable) + .filter(t -> t.getType() == IdentifiableType.BUSBAR_SECTION) + .map(Identifiable::getId) + .toList(); + assertEquals(1, busbarSectionIds.size()); + assertEquals("S1VL1_BBS", busbarSectionIds.get(0)); } @Test diff --git a/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/LimitViolationTest.java b/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/LimitViolationTest.java index 2e7c937cf89..8f4cc2503a4 100644 --- a/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/LimitViolationTest.java +++ b/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/LimitViolationTest.java @@ -87,11 +87,11 @@ void testToString() { LimitViolation limitViolation3 = new LimitViolation("testId", null, LimitViolationType.APPARENT_POWER, null, 6300, 1000, 1, 1100, ThreeSides.THREE); LimitViolation limitViolation4 = new LimitViolation("testId", null, LimitViolationType.LOW_VOLTAGE, - 1000, 1, 1100, new BusBreakerViolationLocation("vlId", "busId")); + 1000, 1, 1100, new BusBreakerViolationLocation(List.of("busId1", "busId2"))); String expected1 = "Subject id: testId, Subject name: null, limitType: HIGH_VOLTAGE, limit: 420.0, limitName: high, acceptableDuration: 2147483647, limitReduction: 1.0, value: 500.0, side: null, voltageLocation: null"; String expected2 = "Subject id: testId, Subject name: null, limitType: CURRENT, limit: 1000.0, limitName: null, acceptableDuration: 6300, limitReduction: 1.0, value: 1100.0, side: ONE, voltageLocation: null"; String expected3 = "Subject id: testId, Subject name: null, limitType: APPARENT_POWER, limit: 1000.0, limitName: null, acceptableDuration: 6300, limitReduction: 1.0, value: 1100.0, side: THREE, voltageLocation: null"; - String expected4 = "Subject id: testId, Subject name: null, limitType: LOW_VOLTAGE, limit: 1000.0, limitName: null, acceptableDuration: 2147483647, limitReduction: 1.0, value: 1100.0, side: null, voltageLocation: BusBreakerViolationLocation{voltageLevelId='vlId', busId='busId'}"; + String expected4 = "Subject id: testId, Subject name: null, limitType: LOW_VOLTAGE, limit: 1000.0, limitName: null, acceptableDuration: 2147483647, limitReduction: 1.0, value: 1100.0, side: null, voltageLocation: BusBreakerViolationLocation{busIds='[busId1, busId2]'}"; assertEquals(expected1, limitViolation1.toString()); assertEquals(expected2, limitViolation2.toString()); assertEquals(expected3, limitViolation3.toString()); diff --git a/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/converter/ExporterTest.java b/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/converter/ExporterTest.java index 90a4a7774c6..d274c92de45 100644 --- a/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/converter/ExporterTest.java +++ b/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/converter/ExporterTest.java @@ -55,8 +55,8 @@ private static SecurityAnalysisResult create() { violation2.addExtension(ActivePowerExtension.class, new ActivePowerExtension(220.0, 230.0)); violation2.addExtension(CurrentExtension.class, new CurrentExtension(95.0)); - LimitViolation violation3 = new LimitViolation("GEN", LimitViolationType.HIGH_VOLTAGE, 100, 0.9f, 110, new BusBreakerViolationLocation("VL", "BUSID")); - LimitViolation violation4 = new LimitViolation("GEN2", LimitViolationType.LOW_VOLTAGE, 100, 0.7f, 115, new NodeBreakerViolationLocation("VL", Arrays.asList("BBS1", "BBS2"))); + LimitViolation violation3 = new LimitViolation("GEN", LimitViolationType.HIGH_VOLTAGE, 100, 0.9f, 110, new BusBreakerViolationLocation(List.of("BUSID"))); + LimitViolation violation4 = new LimitViolation("GEN2", LimitViolationType.LOW_VOLTAGE, 100, 0.7f, 115, new NodeBreakerViolationLocation("VL", Arrays.asList(0, 1))); violation4.addExtension(VoltageExtension.class, new VoltageExtension(400.0)); LimitViolation violation5 = new LimitViolation("NHV1_NHV2_2", LimitViolationType.ACTIVE_POWER, "20'", 1200, 100, 1.0f, 110.0, TwoSides.ONE); diff --git a/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/json/PostContingencyResultTest.java b/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/json/PostContingencyResultTest.java index 8ac679a03af..3f6195f7855 100644 --- a/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/json/PostContingencyResultTest.java +++ b/security-analysis/security-analysis-api/src/test/java/com/powsybl/security/json/PostContingencyResultTest.java @@ -60,9 +60,9 @@ void roundTrip() throws IOException { Contingency contingency = new Contingency("contingency"); LimitViolation violation = new LimitViolation("violation", LimitViolationType.HIGH_VOLTAGE, 420, (float) 0.1, 500); LimitViolation violation2 = new LimitViolation("subject_id", LimitViolationType.HIGH_VOLTAGE, 420, - (float) 0.1, 500, new BusBreakerViolationLocation("vl_id", "bus_id")); + (float) 0.1, 500, new BusBreakerViolationLocation(List.of("bus_id"))); LimitViolation violation3 = new LimitViolation("subject_id", LimitViolationType.LOW_VOLTAGE, 200, - (float) 0.3, 180, new NodeBreakerViolationLocation("vl_id", Collections.singletonList("bbs_id"))); + (float) 0.3, 180, new NodeBreakerViolationLocation("vl_id", Collections.singletonList(0))); LimitViolationsResult result = new LimitViolationsResult(Arrays.asList(violation, violation2, violation3)); List threeWindingsTransformerResults = new ArrayList<>(); threeWindingsTransformerResults.add(new ThreeWindingsTransformerResult("threeWindingsTransformerId", diff --git a/security-analysis/security-analysis-api/src/test/resources/PostContingencyResultTest.json b/security-analysis/security-analysis-api/src/test/resources/PostContingencyResultTest.json index cdda76a6e4a..1e244b657af 100644 --- a/security-analysis/security-analysis-api/src/test/resources/PostContingencyResultTest.json +++ b/security-analysis/security-analysis-api/src/test/resources/PostContingencyResultTest.json @@ -15,8 +15,7 @@ "subjectId" : "subject_id", "violationLocation" : { "type" : "BUS_BREAKER", - "voltageLevelId" : "vl_id", - "busId" : "bus_id" + "busIds" : [ "bus_id" ] }, "limitType" : "HIGH_VOLTAGE", "limit" : 420.0, @@ -27,7 +26,7 @@ "violationLocation" : { "type" : "NODE_BREAKER", "voltageLevelId" : "vl_id", - "busbarIds" : [ "bbs_id" ] + "nodes" : [ 0 ] }, "limitType" : "LOW_VOLTAGE", "limit" : 200.0, diff --git a/security-analysis/security-analysis-api/src/test/resources/SecurityAnalysisResult.json b/security-analysis/security-analysis-api/src/test/resources/SecurityAnalysisResult.json index b6a88c2db22..1d41e66c3b5 100644 --- a/security-analysis/security-analysis-api/src/test/resources/SecurityAnalysisResult.json +++ b/security-analysis/security-analysis-api/src/test/resources/SecurityAnalysisResult.json @@ -105,8 +105,7 @@ "subjectId" : "GEN", "violationLocation" : { "type" : "BUS_BREAKER", - "voltageLevelId" : "VL", - "busId" : "BUSID" + "busIds" : [ "BUSID" ] }, "limitType" : "HIGH_VOLTAGE", "limit" : 100.0, @@ -117,7 +116,7 @@ "violationLocation" : { "type" : "NODE_BREAKER", "voltageLevelId" : "VL", - "busbarIds" : [ "BBS1", "BBS2" ] + "nodes" : [ 0, 1 ] }, "limitType" : "LOW_VOLTAGE", "limit" : 100.0,