From 3cecabf207b65dd03c56338ff8eff5cae4e7e12f Mon Sep 17 00:00:00 2001
From: Sylvestre Prabakaran <38729191+SylvestreSakti@users.noreply.github.com>
Date: Mon, 13 Jan 2025 18:45:24 +0100
Subject: [PATCH] Fix injections ignored on first slack bus when using multiple
 slacks (#1160)

Signed-off-by: PRABAKARAN Sylvestre <sylvestre.prabakaran@rte-france.com>
Co-authored-by: Didier Vidal <didier.vidal_externe@rte-france.com>
---
 .../openloadflow/ac/AcTargetVector.java       |  6 ++-
 .../ac/equations/AcEquationSystemCreator.java |  4 +-
 .../ac/MultipleSlackBusesTest.java            | 48 +++++++++++++++++--
 3 files changed, 50 insertions(+), 8 deletions(-)

diff --git a/src/main/java/com/powsybl/openloadflow/ac/AcTargetVector.java b/src/main/java/com/powsybl/openloadflow/ac/AcTargetVector.java
index 67cbfffac8..df2af8fe4a 100644
--- a/src/main/java/com/powsybl/openloadflow/ac/AcTargetVector.java
+++ b/src/main/java/com/powsybl/openloadflow/ac/AcTargetVector.java
@@ -66,10 +66,14 @@ private static double getReactivePowerControlTarget(LfBranch branch) {
 
     public static void init(Equation<AcVariableType, AcEquationType> equation, LfNetwork network, double[] targets) {
         switch (equation.getType()) {
-            case BUS_TARGET_P, BUS_DISTR_SLACK_P:
+            case BUS_TARGET_P :
                 targets[equation.getColumn()] = network.getBus(equation.getElementNum()).getTargetP();
                 break;
 
+            case BUS_DISTR_SLACK_P :
+                targets[equation.getColumn()] = network.getBus(equation.getElementNum()).getTargetP() - network.getSlackBuses().get(0).getTargetP();
+                break;
+
             case BUS_TARGET_Q:
                 targets[equation.getColumn()] = network.getBus(equation.getElementNum()).getTargetQ();
                 break;
diff --git a/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystemCreator.java b/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystemCreator.java
index bd8e36c279..afef8792a9 100644
--- a/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystemCreator.java
+++ b/src/main/java/com/powsybl/openloadflow/ac/equations/AcEquationSystemCreator.java
@@ -1037,8 +1037,8 @@ private void createMultipleSlackBusesEquations(EquationSystem<AcVariableType, Ac
             for (int i = 1; i < slackBuses.size(); i++) {
                 LfBus slackBus = slackBuses.get(i);
                 // example for 3 slack buses
-                // target_p2 = slack_p2 - slack_p1
-                // target_p3 = slack_p3 - slack_p1
+                // target_p2 - target_p1 = slack_p2 - slack_p1
+                // target_p3 - target_p1 = slack_p3 - slack_p1
                 equationSystem.createEquation(slackBus, AcEquationType.BUS_DISTR_SLACK_P)
                         .addTerms(createActiveInjectionTerms(firstSlackBus, equationSystem.getVariableSet()).stream()
                                 .map(EquationTerm::minus)
diff --git a/src/test/java/com/powsybl/openloadflow/ac/MultipleSlackBusesTest.java b/src/test/java/com/powsybl/openloadflow/ac/MultipleSlackBusesTest.java
index ee57d97d40..bb06f12447 100644
--- a/src/test/java/com/powsybl/openloadflow/ac/MultipleSlackBusesTest.java
+++ b/src/test/java/com/powsybl/openloadflow/ac/MultipleSlackBusesTest.java
@@ -7,6 +7,7 @@
  */
 package com.powsybl.openloadflow.ac;
 
+import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory;
 import com.powsybl.iidm.network.Bus;
 import com.powsybl.iidm.network.Generator;
 import com.powsybl.iidm.network.Line;
@@ -31,6 +32,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 
 import static com.powsybl.openloadflow.util.LoadFlowAssert.assertActivePowerEquals;
@@ -79,6 +81,20 @@ static Stream<Arguments> allModelAndStoppingCriteriaTypes() {
         return Stream.concat(acStream, dcStream);
     }
 
+    static Stream<Arguments> allModelAndSwitchingTypes() {
+        return Stream.of(
+                Arguments.of(true, true),
+                Arguments.of(true, false),
+                Arguments.of(false, true),
+                Arguments.of(false, false)
+        );
+    }
+
+    static Stream<Arguments> allModelTypesAndNbSlackbuses() {
+        return Stream.concat(IntStream.range(1, 5).mapToObj(i -> Arguments.of(true, i)),
+                IntStream.range(1, 5).mapToObj(i -> Arguments.of(false, i)));
+    }
+
     static Stream<Arguments> allModelTypes() {
         return Stream.of(Arguments.of(true), Arguments.of(false));
     }
@@ -148,12 +164,33 @@ void nonImpedantBranchTest(boolean ac) {
         assertSlackBusResults(slackBusResults, expectedSlackBusMismatch, 2);
     }
 
-    @ParameterizedTest(name = "ac : {0}")
-    @MethodSource("allModelTypes")
-    void loadOnSlackBusTest(boolean ac) {
+    @ParameterizedTest(name = "ac : {0}, nbSlackbuses : {1}")
+    @MethodSource("allModelTypesAndNbSlackbuses")
+    void differentNbSlackbusesTest(boolean ac, int nbSlackbuses) {
+        network = IeeeCdfNetworkFactory.create14();
+        parameters.setDc(!ac).setReadSlackBus(false).setDistributedSlack(true);
+        parametersExt.setMaxSlackBusCount(nbSlackbuses) // Testing from 1 to 4 slack buses and expecting same global mismatch
+                .setSlackBusPMaxMismatch(0.001)
+                .setPlausibleActivePowerLimit(10000); // IEEE14 Network has generators with maxP = 9999 we want to keep for distribution
+        LoadFlowResult result = loadFlowRunner.run(network, parameters);
+        double distributedActivePower = result.getComponentResults().get(0).getDistributedActivePower();
+        assertEquals(ac ? -0.006 : -13.4, distributedActivePower, 0.001);
+        assertActivePowerEquals(ac ? -39.996 : -33.300, network.getGenerator("B2-G").getTerminal());
+        assertActivePowerEquals(ac ? 156.886 : 153.453, network.getLine("L1-2-1").getTerminal1());
+        assertActivePowerEquals(ac ? 56.131 : 54.768, network.getLine("L2-4-1").getTerminal1());
+    }
+
+    @ParameterizedTest(name = "ac : {0}, switchSlacks : {1}")
+    @MethodSource("allModelAndSwitchingTypes")
+    void loadOnSlackBusTest(boolean ac, boolean switchSlacks) {
         parameters.setDc(!ac);
         parametersExt.setSlackBusSelectionMode(SlackBusSelectionMode.NAME);
-        parametersExt.setSlackBusesIds(List.of("VLHV2", "VLLOAD"));
+        if (switchSlacks) { //switching slack buses order (expecting the same result)
+            parametersExt.setSlackBusesIds(List.of("VLHV2", "VLLOAD"));
+        } else {
+            parametersExt.setSlackBusesIds(List.of("VLLOAD", "VLHV2"));
+        }
+
         LoadFlowResult result = loadFlowRunner.run(network, parameters);
         assertTrue(result.isFullyConverged());
         LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0);
@@ -161,7 +198,8 @@ void loadOnSlackBusTest(boolean ac) {
         assertEquals(expectedIterationCount, componentResult.getIterationCount());
 
         List<LoadFlowResult.SlackBusResult> slackBusResults = componentResult.getSlackBusResults();
-        assertEquals(List.of("VLHV2_0", "VLLOAD_0"), slackBusResults.stream().map(LoadFlowResult.SlackBusResult::getId).toList());
+        assertEquals(switchSlacks ? List.of("VLHV2_0", "VLLOAD_0") : List.of("VLLOAD_0", "VLHV2_0"),
+                slackBusResults.stream().map(LoadFlowResult.SlackBusResult::getId).toList());
         double expectedSlackBusMismatch = ac ? -0.711 : -3.5;
         assertSlackBusResults(slackBusResults, expectedSlackBusMismatch, 2);