diff --git a/docs/security/parameters.md b/docs/security/parameters.md index cd2476aac8..d83bb133e2 100644 --- a/docs/security/parameters.md +++ b/docs/security/parameters.md @@ -75,3 +75,14 @@ open-security-analysis-default-parameters: ``` At the moment, overriding the parameters by a JSON file is not supported by Open Load Flow. + +## Contingency Parameters + +Some load flow related parameters can be configured per contingency. This can be done by adding to the `Contingency` object an extension of the `ContingencyLoadFlowParameters` class. +Those parameters correspond to parameters present in [`LoadFlowParameters`](inv:powsyblcore:*:*#simulation/loadflow/configuration) and [`OpenLoadFlowParameters`](../loadflow/parameters.md#specific-parameters) : + +- `distributedSlack` (see [distributedSlack](inv:powsyblcore:*:*#simulation/loadflow/configuration)) +- `areaInterchangeControl` (see [areaInterchangeControl](../loadflow/parameters.md#specific-parameters)) +- `balanceType` (see [balanceType](inv:powsyblcore:*:*#simulation/loadflow/configuration)) + +If the extension is added to a contingency, the values of these parameters will override their equivalent of the SA input parameters for post contingency and post remedial actions states calculations. diff --git a/src/main/java/com/powsybl/openloadflow/sa/extensions/ContingencyLoadFlowParameters.java b/src/main/java/com/powsybl/openloadflow/sa/extensions/ContingencyLoadFlowParameters.java new file mode 100644 index 0000000000..c9a17b3a43 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/sa/extensions/ContingencyLoadFlowParameters.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2024, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * 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.openloadflow.sa.extensions; + +import com.powsybl.commons.extensions.AbstractExtension; +import com.powsybl.contingency.Contingency; +import com.powsybl.loadflow.LoadFlowParameters; + +/** + * @author Valentin Mouradian {@literal } + */ +public class ContingencyLoadFlowParameters extends AbstractExtension { + + private boolean distributedSlack; + + private boolean areaInterchangeControl; + + private LoadFlowParameters.BalanceType balanceType; + + public ContingencyLoadFlowParameters(boolean distributedSlack, boolean areaInterchangeControl, LoadFlowParameters.BalanceType balanceType) { + this.distributedSlack = distributedSlack; + this.areaInterchangeControl = areaInterchangeControl; + this.balanceType = balanceType; + } + + @Override + public String getName() { + return "contingency-parameters"; + } + + public boolean isDistributedSlack() { + return distributedSlack; + } + + public boolean isAreaInterchangeControl() { + return areaInterchangeControl; + } + + public LoadFlowParameters.BalanceType getBalanceType() { + return balanceType; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/sa/extensions/ContingencyLoadFlowParametersJsonSerializer.java b/src/main/java/com/powsybl/openloadflow/sa/extensions/ContingencyLoadFlowParametersJsonSerializer.java new file mode 100644 index 0000000000..80d8cc16af --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/sa/extensions/ContingencyLoadFlowParametersJsonSerializer.java @@ -0,0 +1,60 @@ +package com.powsybl.openloadflow.sa.extensions; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.google.auto.service.AutoService; +import com.powsybl.commons.extensions.ExtensionJsonSerializer; +import com.powsybl.commons.json.JsonUtil; +import com.powsybl.contingency.Contingency; + +import java.io.IOException; + +@AutoService(ExtensionJsonSerializer.class) +public class ContingencyLoadFlowParametersJsonSerializer implements ExtensionJsonSerializer { + + @Override + public String getExtensionName() { + return "contingency-parameters"; + } + + @Override + public String getCategoryName() { + return "contingency"; + } + + @Override + public Class getExtensionClass() { + return ContingencyLoadFlowParameters.class; + } + + /** + * Specifies serialization for our extension: ignore name and extendable + */ + private interface SerializationSpec { + + @JsonIgnore + String getName(); + + @JsonIgnore + Contingency getExtendable(); + } + + private static ObjectMapper createMapper() { + return JsonUtil.createObjectMapper() + .addMixIn(ContingencyLoadFlowParameters.class, SerializationSpec.class); + } + + @Override + public void serialize(ContingencyLoadFlowParameters extension, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + createMapper().writeValue(jsonGenerator, extension); + } + + @Override + public ContingencyLoadFlowParameters deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + return createMapper().readValue(jsonParser, ContingencyLoadFlowParameters.class); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/sa/extensions/OpenSecurityAnalysisExtensionsTest.java b/src/test/java/com/powsybl/openloadflow/sa/extensions/OpenSecurityAnalysisExtensionsTest.java new file mode 100644 index 0000000000..02257a0cbd --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/sa/extensions/OpenSecurityAnalysisExtensionsTest.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2024, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * 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.openloadflow.sa.extensions; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.powsybl.commons.json.JsonUtil; +import com.powsybl.commons.test.AbstractSerDeTest; +import com.powsybl.contingency.BranchContingency; +import com.powsybl.contingency.Contingency; +import com.powsybl.contingency.json.ContingencyJsonModule; +import com.powsybl.loadflow.LoadFlowParameters; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Valentin Mouradian {@literal } + */ +class OpenSecurityAnalysisExtensionsTest extends AbstractSerDeTest { + + @Test + void testContingencyLoadFlowParametersExtension() { + Contingency contingency = new Contingency("L2", new BranchContingency("L2")); + contingency.addExtension(ContingencyLoadFlowParameters.class, new ContingencyLoadFlowParameters(false, true, LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD)); + + ContingencyLoadFlowParameters contingencyLoadFlowParameters = contingency.getExtension(ContingencyLoadFlowParameters.class); + + assertEquals(contingencyLoadFlowParameters, contingency.getExtensionByName("contingency-parameters")); + assertFalse(contingencyLoadFlowParameters.isDistributedSlack()); + assertTrue(contingencyLoadFlowParameters.isAreaInterchangeControl()); + assertEquals(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD, contingencyLoadFlowParameters.getBalanceType()); + } + + @Test + void testContingencyLoadFlowParametersExtensionJson() throws IOException { + Contingency contingency = new Contingency("L2", new BranchContingency("L2")); + contingency.addExtension(ContingencyLoadFlowParameters.class, new ContingencyLoadFlowParameters(false, true, LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD)); + assertEquals(ContingencyLoadFlowParameters.class, new ContingencyLoadFlowParametersJsonSerializer().getExtensionClass()); + roundTripTest(contingency, OpenSecurityAnalysisExtensionsTest::writeContingency, OpenSecurityAnalysisExtensionsTest::readContingency, "/contingencies.json"); + } + + public static Contingency readContingency(Path jsonFile) { + Objects.requireNonNull(jsonFile); + + try (InputStream is = Files.newInputStream(jsonFile)) { + ObjectMapper objectMapper = JsonUtil.createObjectMapper(); + ContingencyJsonModule module = new ContingencyJsonModule(); + objectMapper.registerModule(module); + + return objectMapper.readValue(is, Contingency.class); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static void writeContingency(Contingency contingency, Path jsonFile) { + Objects.requireNonNull(contingency); + Objects.requireNonNull(jsonFile); + + try (OutputStream os = Files.newOutputStream(jsonFile)) { + ObjectMapper mapper = JsonUtil.createObjectMapper(); + ContingencyJsonModule module = new ContingencyJsonModule(); + mapper.registerModule(module); + + ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter(); + writer.writeValue(os, contingency); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/src/test/resources/contingencies.json b/src/test/resources/contingencies.json new file mode 100644 index 0000000000..771a4c21e6 --- /dev/null +++ b/src/test/resources/contingencies.json @@ -0,0 +1,14 @@ +{ + "id" : "L2", + "elements" : [ { + "id" : "L2", + "type" : "BRANCH" + } ], + "extensions" : { + "contingency-parameters" : { + "distributedSlack" : false, + "areaInterchangeControl" : true, + "balanceType" : "PROPORTIONAL_TO_LOAD" + } + } +} \ No newline at end of file