From 2783c8f93db19f34cd77a9428c66e5550f6ac9fd Mon Sep 17 00:00:00 2001 From: dbraquart <107846716+dbraquart@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:21:53 +0100 Subject: [PATCH] Take into account battery balance in generator dispatch (#392) Signed-off-by: David BRAQUART --- .../modifications/GenerationDispatch.java | 14 +- .../modifications/GenerationDispatchTest.java | 91 +++-- .../testGenerationDispatchWithBatteries.xiidm | 314 ++++++++++++++++++ 3 files changed, 398 insertions(+), 21 deletions(-) create mode 100644 src/test/resources/testGenerationDispatchWithBatteries.xiidm diff --git a/src/main/java/org/gridsuite/modification/server/modifications/GenerationDispatch.java b/src/main/java/org/gridsuite/modification/server/modifications/GenerationDispatch.java index dc4a0270f..a835c3e2b 100644 --- a/src/main/java/org/gridsuite/modification/server/modifications/GenerationDispatch.java +++ b/src/main/java/org/gridsuite/modification/server/modifications/GenerationDispatch.java @@ -82,6 +82,14 @@ private static double computeTotalDemand(Component component, double lossCoeffic return totalLoad * (1. + lossCoefficient / 100.); } + private static double computeTotalActiveBatteryTargetP(Component component) { + Objects.requireNonNull(component); + return component.getBusStream().flatMap(Bus::getBatteryStream) + .filter(battery -> battery.getTerminal().isConnected()) + .mapToDouble(Battery::getTargetP) + .sum(); + } + private static double computeTotalAmountFixedSupply(Network network, Component component, List generatorsWithFixedSupply, Reporter reporter) { double totalAmountFixedSupply = 0.; List generatorsWithoutSetpointList = new ArrayList<>(); @@ -505,7 +513,11 @@ public void apply(Network network, Reporter subReporter) { report(powerToDispatchReporter, Integer.toString(componentNum), "TotalOutwardHvdcFlow", "The HVDC balance is : ${hvdcBalance} MW", Map.of("hvdcBalance", round(hvdcBalance)), TypedValue.INFO_SEVERITY); - double totalAmountSupplyToBeDispatched = totalDemand - totalAmountFixedSupply - hvdcBalance; + double activeBatteryTotalTargetP = computeTotalActiveBatteryTargetP(component); + report(powerToDispatchReporter, Integer.toString(componentNum), "TotalActiveBatteryTargetP", "The battery balance is : ${batteryBalance} MW", + Map.of("batteryBalance", round(activeBatteryTotalTargetP)), TypedValue.INFO_SEVERITY); + + double totalAmountSupplyToBeDispatched = totalDemand - totalAmountFixedSupply - hvdcBalance - activeBatteryTotalTargetP; if (totalAmountSupplyToBeDispatched < 0.) { report(powerToDispatchReporter, Integer.toString(componentNum), "TotalAmountFixedSupplyExceedsTotalDemand", "The total amount of fixed supply exceeds the total demand", Map.of(), TypedValue.WARN_SEVERITY); diff --git a/src/test/java/org/gridsuite/modification/server/modifications/GenerationDispatchTest.java b/src/test/java/org/gridsuite/modification/server/modifications/GenerationDispatchTest.java index 967e75cf4..865804c5f 100644 --- a/src/test/java/org/gridsuite/modification/server/modifications/GenerationDispatchTest.java +++ b/src/test/java/org/gridsuite/modification/server/modifications/GenerationDispatchTest.java @@ -23,10 +23,9 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MvcResult; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.*; import java.util.stream.Collectors; import static org.gridsuite.modification.server.utils.TestUtils.assertLogMessage; @@ -34,6 +33,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -47,6 +47,9 @@ public class GenerationDispatchTest extends AbstractNetworkModificationTest { private static final String GH3_ID = "GH3"; private static final String GTH1_ID = "GTH1"; private static final String GTH2_ID = "GTH2"; + private static final String BATTERY1_ID = "BATTERY1"; + private static final String BATTERY2_ID = "BATTERY2"; + private static final String BATTERY3_ID = "BATTERY3"; private static final String TEST1_ID = "TEST1"; private static final String GROUP1_ID = "GROUP1"; private static final String GROUP2_ID = "GROUP2"; @@ -90,11 +93,34 @@ private FilterEquipments getFilterEquipments(UUID filterID, String filterName, .build(); } + private void assertLogReportsForDefaultNetwork(double batteryBalanceOnSc2) { + int firstSynchronousComponentNum = getNetwork().getGenerator(GTH1_ID).getTerminal().getBusView().getBus().getSynchronousComponent().getNum(); // GTH1 is in first synchronous component + assertLogMessage("The total demand is : 528.0 MW", "TotalDemand" + firstSynchronousComponentNum, reportService); + assertLogMessage("The total amount of fixed supply is : 0.0 MW", "TotalAmountFixedSupply" + firstSynchronousComponentNum, reportService); + assertLogMessage("The HVDC balance is : 90.0 MW", "TotalOutwardHvdcFlow" + firstSynchronousComponentNum, reportService); + assertLogMessage("The battery balance is : 0.0 MW", "TotalActiveBatteryTargetP" + firstSynchronousComponentNum, reportService); + assertLogMessage("The total amount of supply to be dispatched is : 438.0 MW", "TotalAmountSupplyToBeDispatched" + firstSynchronousComponentNum, reportService); + assertLogMessage("The supply-demand balance could not be met : the remaining power imbalance is 138.0 MW", "SupplyDemandBalanceCouldNotBeMet" + firstSynchronousComponentNum, reportService); + // on SC 2, we have to substract the battery balance + final double defaultTotalAmount = 330.0; + DecimalFormat df = new DecimalFormat("#0.0", new DecimalFormatSymbols(Locale.US)); + final String totalAmount = df.format(defaultTotalAmount - batteryBalanceOnSc2); + int secondSynchronousComponentNum = getNetwork().getGenerator(GH1_ID).getTerminal().getBusView().getBus().getSynchronousComponent().getNum(); // GH1 is in second synchronous component + assertLogMessage("The total demand is : 240.0 MW", "TotalDemand" + secondSynchronousComponentNum, reportService); + assertLogMessage("The total amount of fixed supply is : 0.0 MW", "TotalAmountFixedSupply" + secondSynchronousComponentNum, reportService); + assertLogMessage("The HVDC balance is : -90.0 MW", "TotalOutwardHvdcFlow" + secondSynchronousComponentNum, reportService); + assertLogMessage("The battery balance is : " + df.format(batteryBalanceOnSc2) + " MW", "TotalActiveBatteryTargetP" + secondSynchronousComponentNum, reportService); + assertLogMessage("The total amount of supply to be dispatched is : " + totalAmount + " MW", "TotalAmountSupplyToBeDispatched" + secondSynchronousComponentNum, reportService); + assertLogMessage("Marginal cost: 150.0", "MaxUsedMarginalCost" + secondSynchronousComponentNum, reportService); + assertLogMessage("The supply-demand balance could be met", "SupplyDemandBalanceCouldBeMet" + secondSynchronousComponentNum, reportService); + assertLogMessage("Sum of generator active power setpoints in SOUTH region: " + totalAmount + " MW (NUCLEAR: 0.0 MW, THERMAL: 0.0 MW, HYDRO: " + totalAmount + " MW, WIND AND SOLAR: 0.0 MW, OTHER: 0.0 MW).", "SumGeneratorActivePowerSOUTH" + secondSynchronousComponentNum, reportService); + } + @Test public void testGenerationDispatch() throws Exception { ModificationInfos modification = buildModification(); - // network with 2 synchronous components, 2 hvdc lines between them and no forcedOutageRate and plannedOutageRate for the generators + // network with 2 synchronous components, no battery, 2 hvdc lines between them and no forcedOutageRate and plannedOutageRate for the generators setNetwork(Network.read("testGenerationDispatch.xiidm", getClass().getResourceAsStream("/testGenerationDispatch.xiidm"))); String modificationJson = mapper.writeValueAsString(modification); @@ -103,22 +129,47 @@ public void testGenerationDispatch() throws Exception { assertNetworkAfterCreationWithStandardLossCoefficient(); - // test total demand and remaining power imbalance on synchronous components - int firstSynchronousComponentNum = getNetwork().getGenerator(GTH1_ID).getTerminal().getBusView().getBus().getSynchronousComponent().getNum(); // GTH1 is in first synchronous component - assertLogMessage("The total demand is : 528.0 MW", "TotalDemand" + firstSynchronousComponentNum, reportService); - assertLogMessage("The total amount of fixed supply is : 0.0 MW", "TotalAmountFixedSupply" + firstSynchronousComponentNum, reportService); - assertLogMessage("The HVDC balance is : 90.0 MW", "TotalOutwardHvdcFlow" + firstSynchronousComponentNum, reportService); - assertLogMessage("The total amount of supply to be dispatched is : 438.0 MW", "TotalAmountSupplyToBeDispatched" + firstSynchronousComponentNum, reportService); - assertLogMessage("The supply-demand balance could not be met : the remaining power imbalance is 138.0 MW", "SupplyDemandBalanceCouldNotBeMet" + firstSynchronousComponentNum, reportService); + assertLogReportsForDefaultNetwork(0.); + } - int secondSynchronousComponentNum = getNetwork().getGenerator(GH1_ID).getTerminal().getBusView().getBus().getSynchronousComponent().getNum(); // GH1 is in second synchronous component - assertLogMessage("The total demand is : 240.0 MW", "TotalDemand" + secondSynchronousComponentNum, reportService); - assertLogMessage("The total amount of fixed supply is : 0.0 MW", "TotalAmountFixedSupply" + secondSynchronousComponentNum, reportService); - assertLogMessage("The HVDC balance is : -90.0 MW", "TotalOutwardHvdcFlow" + secondSynchronousComponentNum, reportService); - assertLogMessage("The total amount of supply to be dispatched is : 330.0 MW", "TotalAmountSupplyToBeDispatched" + secondSynchronousComponentNum, reportService); - assertLogMessage("Marginal cost: 150.0", "MaxUsedMarginalCost" + secondSynchronousComponentNum, reportService); - assertLogMessage("The supply-demand balance could be met", "SupplyDemandBalanceCouldBeMet" + secondSynchronousComponentNum, reportService); - assertLogMessage("Sum of generator active power setpoints in SOUTH region: 330.0 MW (NUCLEAR: 0.0 MW, THERMAL: 0.0 MW, HYDRO: 330.0 MW, WIND AND SOLAR: 0.0 MW, OTHER: 0.0 MW).", "SumGeneratorActivePowerSOUTH" + secondSynchronousComponentNum, reportService); + @Test + public void testGenerationDispatchWithBattery() throws Exception { + ModificationInfos modification = buildModification(); + + // same than testGenerationDispatch, with 3 Batteries (in 2nd SC) + setNetwork(Network.read("testGenerationDispatchWithBatteries.xiidm", getClass().getResourceAsStream("/testGenerationDispatchWithBatteries.xiidm"))); + // only 2 are connected + assertTrue(getNetwork().getBattery(BATTERY1_ID).getTerminal().isConnected()); + assertTrue(getNetwork().getBattery(BATTERY2_ID).getTerminal().isConnected()); + assertFalse(getNetwork().getBattery(BATTERY3_ID).getTerminal().isConnected()); + final double batteryTotalTargetP = getNetwork().getBattery(BATTERY1_ID).getTargetP() + getNetwork().getBattery(BATTERY2_ID).getTargetP(); + + String modificationJson = mapper.writeValueAsString(modification); + mockMvc.perform(post(getNetworkModificationUri()).content(modificationJson).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + assertLogReportsForDefaultNetwork(batteryTotalTargetP); + } + + @Test + public void testGenerationDispatchWithBatteryConnection() throws Exception { + ModificationInfos modification = buildModification(); + + // network with 3 Batteries (in 2nd SC) + setNetwork(Network.read("testGenerationDispatch.xiidm", getClass().getResourceAsStream("/testGenerationDispatchWithBatteries.xiidm"))); + // connect the 3rd one + assertTrue(getNetwork().getBattery(BATTERY1_ID).getTerminal().isConnected()); + assertTrue(getNetwork().getBattery(BATTERY2_ID).getTerminal().isConnected()); + assertFalse(getNetwork().getBattery(BATTERY3_ID).getTerminal().isConnected()); + assertTrue(getNetwork().getBattery(BATTERY3_ID).getTargetP() > 0); + getNetwork().getBattery(BATTERY3_ID).getTerminal().connect(); + final double batteryTotalTargetP = getNetwork().getBattery(BATTERY1_ID).getTargetP() + getNetwork().getBattery(BATTERY2_ID).getTargetP() + getNetwork().getBattery(BATTERY3_ID).getTargetP(); + + String modificationJson = mapper.writeValueAsString(modification); + mockMvc.perform(post(getNetworkModificationUri()).content(modificationJson).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + assertLogReportsForDefaultNetwork(batteryTotalTargetP); } @Test diff --git a/src/test/resources/testGenerationDispatchWithBatteries.xiidm b/src/test/resources/testGenerationDispatchWithBatteries.xiidm new file mode 100644 index 000000000..15a155c09 --- /dev/null +++ b/src/test/resources/testGenerationDispatchWithBatteries.xiidm @@ -0,0 +1,314 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +