diff --git a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysis.java b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysis.java index bc5f2b04ff9..15317035c45 100644 --- a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysis.java +++ b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysis.java @@ -113,7 +113,7 @@ public CompletableFuture runAsync(Network network, SensitivityResultModelWriter resultWriter = new SensitivityResultModelWriter(runParameters.getContingencies()); return provider.run(network, workingVariantId, factorReader, resultWriter, runParameters) - .thenApply(unused -> new SensitivityAnalysisResult(factors, resultWriter.getContingencyStatuses(), resultWriter.getValues())); + .thenApply(unused -> new SensitivityAnalysisResult(factors, resultWriter.getContingencyStatuses(), resultWriter.getPreContingencyStatuses(), resultWriter.getValues())); } public CompletableFuture runAsync(Network network, diff --git a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysisResult.java b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysisResult.java index e9a4e8ed1d9..7ac12b1d671 100644 --- a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysisResult.java +++ b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysisResult.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.core.JsonToken; import com.powsybl.commons.PowsyblException; import com.powsybl.contingency.Contingency; +import com.powsybl.loadflow.LoadFlowResult; import org.jgrapht.alg.util.Triple; import java.io.IOException; @@ -47,6 +48,8 @@ public class SensitivityAnalysisResult { private final List contingencyStatuses; + private final List preContingencyStatuses; + private final List values; private final Map> valuesByContingencyId = new HashMap<>(); @@ -57,6 +60,14 @@ public class SensitivityAnalysisResult { private final Map statusByContingencyId = new HashMap<>(); + /** + * The load flow status for the component + * @param status + * @param statusText + */ + public record LoadFlowStatus(LoadFlowResult.ComponentResult.Status status, String statusText) { + } + public enum Status { SUCCESS, FAILURE, @@ -65,9 +76,21 @@ public enum Status { public static class SensitivityContingencyStatus { + static final String COMPONENTS_LOADFLOW_STATUSES = "componentsLoadFlowStatuses"; + static final String LOAD_FLOW_STATUS = "loadFlowStatus"; + static final String LOAD_FLOW_STATUS_DESCRIPTION = "loadFlowStatusDescription"; + static final String NUM_CC = "numCC"; + static final String NUM_CS = "numCS"; + static final String CONTINGENCY_ID = "contingencyId"; + static final String CONTINGENCY_STATUS = "contingencyStatus"; + private final String contingencyId; private final Status status; + /** + * Load flow status for all the components + */ + private final List> componentsLoadFlowStatusList; public String getContingencyId() { return contingencyId; @@ -77,16 +100,37 @@ public Status getStatus() { return status; } + public Collection> getComponentsLoadFlowStatusList() { + return componentsLoadFlowStatusList; + } + public SensitivityContingencyStatus(String contingencyId, Status status) { this.contingencyId = Objects.requireNonNull(contingencyId); this.status = Objects.requireNonNull(status); + this.componentsLoadFlowStatusList = new ArrayList<>(); + } + + public void addComponentLoadFlowStatus(LoadFlowStatus loadFlowStatus, int numCC, int numCs) { + componentsLoadFlowStatusList.add(Triple.of(loadFlowStatus, numCC, numCs)); } public static void writeJson(JsonGenerator jsonGenerator, SensitivityContingencyStatus contingencyStatus) { try { jsonGenerator.writeStartObject(); - jsonGenerator.writeStringField("contingencyId", contingencyStatus.getContingencyId()); - jsonGenerator.writeStringField("contingencyStatus", contingencyStatus.status.name()); + jsonGenerator.writeStringField(CONTINGENCY_ID, contingencyStatus.getContingencyId()); + jsonGenerator.writeStringField(CONTINGENCY_STATUS, contingencyStatus.status.name()); + if (!contingencyStatus.getComponentsLoadFlowStatusList().isEmpty()) { + jsonGenerator.writeArrayFieldStart(COMPONENTS_LOADFLOW_STATUSES); + for (Triple componentLoadflowStatus : contingencyStatus.getComponentsLoadFlowStatusList()) { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField(LOAD_FLOW_STATUS, componentLoadflowStatus.getFirst().status.toString()); + jsonGenerator.writeStringField(LOAD_FLOW_STATUS_DESCRIPTION, componentLoadflowStatus.getFirst().statusText); + jsonGenerator.writeNumberField(NUM_CC, componentLoadflowStatus.getSecond()); + jsonGenerator.writeNumberField(NUM_CS, componentLoadflowStatus.getThird()); + jsonGenerator.writeEndObject(); + } + jsonGenerator.writeEndArray(); + } jsonGenerator.writeEndObject(); } catch (IOException e) { throw new UncheckedIOException(e); @@ -94,8 +138,9 @@ public static void writeJson(JsonGenerator jsonGenerator, SensitivityContingency } static final class ParsingContext { - private Contingency contingency; + protected Contingency contingency; private Status status; + protected List> componentLoadflowStatuses; } public static SensitivityContingencyStatus parseJson(JsonParser parser) { @@ -108,7 +153,7 @@ public static SensitivityContingencyStatus parseJson(JsonParser parser) { if (token == JsonToken.FIELD_NAME) { parseJson(parser, context); } else if (token == JsonToken.END_OBJECT) { - return new SensitivityContingencyStatus(context.contingency != null ? context.contingency.getId() : "", context.status); + return getSensitivityContingencyStatus(context); } } } catch (IOException e) { @@ -117,20 +162,221 @@ public static SensitivityContingencyStatus parseJson(JsonParser parser) { throw new PowsyblException("Parsing error"); } - private static void parseJson(JsonParser parser, SensitivityContingencyStatus.ParsingContext context) throws IOException { + private static SensitivityContingencyStatus getSensitivityContingencyStatus(ParsingContext context) { + SensitivityContingencyStatus sensitivityContingencyStatus = new SensitivityContingencyStatus(context.contingency != null ? context.contingency.getId() : "", context.status); + if (context.componentLoadflowStatuses != null) { + for (var triple : context.componentLoadflowStatuses) { + sensitivityContingencyStatus.addComponentLoadFlowStatus(triple.getFirst(), triple.getSecond(), triple.getThird()); + } + } + return sensitivityContingencyStatus; + } + + static void parseJson(JsonParser parser, SensitivityContingencyStatus.ParsingContext context) throws IOException { String fieldName = parser.currentName(); + switch (fieldName) { - case "contingencyId": + case CONTINGENCY_ID -> { parser.nextToken(); context.contingency = new Contingency(parser.getValueAsString()); - break; - case "contingencyStatus": + } + case CONTINGENCY_STATUS -> { parser.nextToken(); context.status = Status.valueOf(parser.getValueAsString()); - break; - default: - throw new PowsyblException("Unexpected field: " + fieldName); + } + case COMPONENTS_LOADFLOW_STATUSES -> { + context.componentLoadflowStatuses = parseComponentLoadflowStatuses(parser); + } + default -> throw new PowsyblException("Unexpected field: " + fieldName); + } + } + + private static List> parseComponentLoadflowStatuses(JsonParser parser) throws IOException { + if (parser.nextToken() != JsonToken.START_ARRAY) { + throw new PowsyblException("Expected start of array for component loadflow statuses"); } + + List> statuses = new ArrayList<>(); + while (parser.nextToken() != JsonToken.END_ARRAY) { + if (parser.currentToken() == JsonToken.START_OBJECT) { + statuses.add(parseSingleComponentStatus(parser)); + } + } + return statuses; + } + + private static Triple parseSingleComponentStatus(JsonParser parser) throws IOException { + String statusStr = null; + String descStr = null; + int numCC = 0; + int numCS = 0; + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.currentName(); + parser.nextToken(); // Move to value + + switch (fieldName) { + case LOAD_FLOW_STATUS -> statusStr = parser.getText(); + case LOAD_FLOW_STATUS_DESCRIPTION -> descStr = parser.getText(); + case NUM_CC -> numCC = parser.getIntValue(); + case NUM_CS -> numCS = parser.getIntValue(); + default -> parser.skipChildren(); + } + } + + LoadFlowStatus lfs = new LoadFlowStatus(com.powsybl.loadflow.LoadFlowResult.ComponentResult.Status.valueOf(statusStr), descStr); + return new Triple<>(lfs, numCC, numCS); + } + + } + + public static class SensitivityPreContingencyStatus { + + public static final String LOAD_FLOW_STATUS = "loadFlowStatus"; + public static final String STATUS = "status"; + public static final String STATUS_TEXT = "statusText"; + public static final String NUM_CC = "numCC"; + public static final String NUM_CS = "numCS"; + + private LoadFlowStatus loadFlowStatus; + private Integer numCC; + private Integer numCS; + + public SensitivityPreContingencyStatus(LoadFlowStatus loadFlowStatus, int numCC, int numCS) { + this.loadFlowStatus = loadFlowStatus; + this.numCC = numCC; + this.numCS = numCS; + } + + public SensitivityPreContingencyStatus() { + } + + public void setLoadFlowStatus(LoadFlowStatus loadFlowStatus) { + this.loadFlowStatus = loadFlowStatus; + } + + public void setNumCC(Integer numCC) { + this.numCC = numCC; + } + + public void setNumCS(Integer numCS) { + this.numCS = numCS; + } + + public LoadFlowStatus getLoadFlowStatus() { + return loadFlowStatus; + } + + public Integer getNumCC() { + return numCC; + } + + public Integer getNumCS() { + return numCS; + } + + public static void writeJson(JsonGenerator jsonGenerator, SensitivityPreContingencyStatus precontingencyStatus) { + try { + jsonGenerator.writeStartObject(); + jsonGenerator.writeNumberField(NUM_CC, precontingencyStatus.getNumCC()); + jsonGenerator.writeNumberField(NUM_CS, precontingencyStatus.getNumCS()); + jsonGenerator.writeObjectFieldStart(LOAD_FLOW_STATUS); + jsonGenerator.writeStringField(STATUS, precontingencyStatus.getLoadFlowStatus().status.toString()); + jsonGenerator.writeStringField(STATUS_TEXT, precontingencyStatus.getLoadFlowStatus().statusText); + jsonGenerator.writeEndObject(); + jsonGenerator.writeEndObject(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + static final class ParsingContext { + protected List preContingencyloadflowStatuses; + } + + public static List parseJson(JsonParser parser) { + Objects.requireNonNull(parser); + + var context = new SensitivityPreContingencyStatus.ParsingContext(); + try { + JsonToken token; + while ((token = parser.nextToken()) != null) { + if (token == JsonToken.FIELD_NAME) { + parseJson(parser, context); + } else if (token == JsonToken.END_OBJECT) { + return getSensitivityPreContingencyStatus(context); + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + throw new PowsyblException("Parsing error"); + } + + private static List getSensitivityPreContingencyStatus(ParsingContext context) { + return context.preContingencyloadflowStatuses; + } + + protected static void parseJson(JsonParser parser, SensitivityPreContingencyStatus.ParsingContext context) throws IOException { + if (parser.nextToken() != JsonToken.START_ARRAY) { + throw new PowsyblException("Expected an array of objects"); + } + + context.preContingencyloadflowStatuses = new ArrayList<>(); + + while (parser.nextToken() != JsonToken.END_ARRAY) { + if (parser.getCurrentToken() == JsonToken.START_OBJECT) { + context.preContingencyloadflowStatuses.add(parseComponentStatus(parser)); + } + } + } + + private static SensitivityPreContingencyStatus parseComponentStatus(JsonParser parser) throws IOException { + SensitivityPreContingencyStatus componentStatus = new SensitivityPreContingencyStatus(); + LoadFlowStatusHolder holder = new LoadFlowStatusHolder(); + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.currentName(); + parser.nextToken(); // Move to value + + switch (fieldName) { + case NUM_CC -> componentStatus.setNumCC(parser.getIntValue()); + case NUM_CS -> componentStatus.setNumCS(parser.getIntValue()); + case LOAD_FLOW_STATUS -> parseLoadFlowStatus(parser, holder); + default -> parser.skipChildren(); + } + } + + if (holder.value != null) { + var status = com.powsybl.loadflow.LoadFlowResult.ComponentResult.Status.valueOf(holder.value); + componentStatus.setLoadFlowStatus(new LoadFlowStatus(status, holder.text)); + } + + return componentStatus; + } + + private static void parseLoadFlowStatus(JsonParser parser, LoadFlowStatusHolder holder) throws IOException { + if (parser.currentToken() != JsonToken.START_OBJECT) { + throw new PowsyblException(LOAD_FLOW_STATUS + " should be an object"); + } + + while (parser.nextToken() != JsonToken.END_OBJECT) { + String fieldName = parser.currentName(); + parser.nextToken(); + + if (STATUS.equals(fieldName)) { + holder.value = parser.getText(); + } else if (STATUS_TEXT.equals(fieldName)) { + holder.text = parser.getText(); + } else { + parser.skipChildren(); + } + } + } + + private static final class LoadFlowStatusHolder { + String value; + String text; } } @@ -138,11 +384,13 @@ private static void parseJson(JsonParser parser, SensitivityContingencyStatus.Pa * Sensitivity analysis result * @param factors the list of sensitivity factors that have been computed. * @param contingencyStatuses the list of contingencies and their associated computation status. + * @param preContingencyStatuses the list of pre contingencies and their associated loadflow status. * @param values result values of the sensitivity analysis in pre-contingency state and post-contingency states. */ - public SensitivityAnalysisResult(List factors, List contingencyStatuses, List values) { + public SensitivityAnalysisResult(List factors, List contingencyStatuses, List preContingencyStatuses, List values) { this.factors = Collections.unmodifiableList(Objects.requireNonNull(factors)); this.contingencyStatuses = Collections.unmodifiableList(Objects.requireNonNull(contingencyStatuses)); + this.preContingencyStatuses = Collections.unmodifiableList(Objects.requireNonNull(preContingencyStatuses)); this.values = Collections.unmodifiableList(Objects.requireNonNull(values)); for (SensitivityValue value : values) { SensitivityFactor factor = factors.get(value.getFactorIndex()); @@ -176,6 +424,10 @@ public List getContingencyStatuses() { return contingencyStatuses; } + public List getPreContingencyStatuses() { + return preContingencyStatuses; + } + /** * Get a list of all the sensitivity values. * diff --git a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysisTool.java b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysisTool.java index 073cc24d713..bc9f4e58fba 100644 --- a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysisTool.java +++ b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityAnalysisTool.java @@ -24,7 +24,6 @@ import com.powsybl.contingency.json.ContingencyJsonModule; import com.powsybl.iidm.network.ImportConfig; import com.powsybl.iidm.network.Network; -import com.powsybl.iidm.network.tools.ConversionToolUtils; import com.powsybl.sensitivity.json.JsonSensitivityAnalysisParameters; import com.powsybl.sensitivity.json.SensitivityJsonModule; import com.powsybl.tools.Command; @@ -59,6 +58,8 @@ public class SensitivityAnalysisTool implements Tool { private static final String VARIABLE_SETS_FILE_OPTION = "variable-sets-file"; private static final String PARAMETERS_FILE = "parameters-file"; private static final String OUTPUT_CONTINGENCY_STATUS_FILE_OPTION = "output-contingency-file"; + private static final String OUTPUT_PRE_CONTINGENCY_STATUS_FILE_OPTION = "output-pre-contingency-file"; + private static final String SINGLE_OUTPUT = "single-output"; @Override @@ -115,6 +116,11 @@ public Options getOptions() { .hasArg() .argName("FILE") .build()); + options.addOption(Option.builder().longOpt(OUTPUT_PRE_CONTINGENCY_STATUS_FILE_OPTION) + .desc("pre contingency status output path (csv only)") + .hasArg() + .argName("FILE") + .build()); options.addOption(Option.builder().longOpt(SINGLE_OUTPUT) .desc("Output sensitivity analysis results in a single json file using output file option (values, factors and contingency status).") .build()); @@ -150,33 +156,50 @@ private static String buildContingencyStatusPath(String outputFile) { return outputFile.replace(".csv", "_contingency_status.csv"); } + private static String buildPreContingencyStatusPath(String outputFile) { + return outputFile.replace(".csv", "_pre_contingency_status.csv"); + } + @Override public void run(CommandLine line, ToolRunningContext context) throws Exception { Path caseFile = context.getFileSystem().getPath(line.getOptionValue(CASE_FILE_OPTION)); Path outputFile = context.getFileSystem().getPath(line.getOptionValue(OUTPUT_FILE_OPTION)); boolean csv = isCsv(outputFile); Path outputFileStatus = null; - + Path outputFilePreContingencyStatus = null; if (csv) { if (line.hasOption(OUTPUT_CONTINGENCY_STATUS_FILE_OPTION)) { outputFileStatus = context.getFileSystem().getPath(line.getOptionValue(OUTPUT_CONTINGENCY_STATUS_FILE_OPTION)); } else { outputFileStatus = context.getFileSystem().getPath(buildContingencyStatusPath(line.getOptionValue(OUTPUT_FILE_OPTION))); } + if (line.hasOption(OUTPUT_PRE_CONTINGENCY_STATUS_FILE_OPTION)) { + outputFilePreContingencyStatus = context.getFileSystem().getPath(line.getOptionValue(OUTPUT_PRE_CONTINGENCY_STATUS_FILE_OPTION)); + } else { + outputFilePreContingencyStatus = context.getFileSystem().getPath(buildPreContingencyStatusPath(line.getOptionValue(OUTPUT_FILE_OPTION))); + } boolean contingencyCsv = isCsv(outputFileStatus); if (!contingencyCsv) { - throw new PowsyblException(OUTPUT_FILE_OPTION + " and " + OUTPUT_CONTINGENCY_STATUS_FILE_OPTION + " files must have the same format (csv)."); + throw new PowsyblException(OUTPUT_FILE_OPTION + " and " + OUTPUT_CONTINGENCY_STATUS_FILE_OPTION + " and " + OUTPUT_PRE_CONTINGENCY_STATUS_FILE_OPTION + " files must have the same format (csv)."); } if (line.hasOption(SINGLE_OUTPUT)) { throw new PowsyblException("Unsupported " + SINGLE_OUTPUT + " option does not support csv file as argument of " + OUTPUT_FILE_OPTION + ". Must be json."); } + } else { + // json format + if (line.hasOption(OUTPUT_CONTINGENCY_STATUS_FILE_OPTION)) { + throw new PowsyblException(OUTPUT_CONTINGENCY_STATUS_FILE_OPTION + " file is not supported in json"); + } + if (line.hasOption(OUTPUT_PRE_CONTINGENCY_STATUS_FILE_OPTION)) { + throw new PowsyblException(OUTPUT_PRE_CONTINGENCY_STATUS_FILE_OPTION + " file is not supported in json"); + } } Path factorsFile = context.getFileSystem().getPath(line.getOptionValue(FACTORS_FILE_OPTION)); context.getOutputStream().println("Loading network '" + caseFile + "'"); - Properties inputParams = readProperties(line, ConversionToolUtils.OptionType.IMPORT, context); + Properties inputParams = readProperties(line, OptionType.IMPORT, context); Network network = Network.read(caseFile, context.getShortTimeExecutionComputationManager(), ImportConfig.load(), inputParams); if (network == null) { throw new PowsyblException("Case '" + caseFile + "' not found"); @@ -210,7 +233,7 @@ public void run(CommandLine line, ToolRunningContext context) throws Exception { Stopwatch stopwatch = Stopwatch.createStarted(); try (ComputationManager computationManager = DefaultComputationManagerConfig.load().createLongTimeExecutionComputationManager()) { SensitivityAnalysisParametersRecord parametersRecord = new SensitivityAnalysisParametersRecord(factorsReader, params, network, contingencies, - variableSets, computationManager, outputFile, outputFileStatus, csv); + variableSets, computationManager, outputFile, outputFileStatus, outputFilePreContingencyStatus, csv); run(line, parametersRecord); } context.getOutputStream().println("Analysis done in " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + " ms"); @@ -224,6 +247,7 @@ private record SensitivityAnalysisParametersRecord(SensitivityFactorJsonReader f ComputationManager computationManager, Path outputFile, Path outputFileStatus, + Path outputFilePreContingencyStatus, boolean csv) { } @@ -243,10 +267,15 @@ private void run(CommandLine line, SensitivityAnalysisParametersRecord parameter } else { if (parametersRecord.csv) { try (Writer writer = Files.newBufferedWriter(parametersRecord.outputFile, StandardCharsets.UTF_8); - Writer writerStatuses = Files.newBufferedWriter(parametersRecord.outputFileStatus, StandardCharsets.UTF_8); + Writer writerComponentsStatuses = Files.newBufferedWriter(parametersRecord.outputFileStatus, StandardCharsets.UTF_8); + Writer writerPreContingencyStatuses = Files.newBufferedWriter(parametersRecord.outputFilePreContingencyStatus, StandardCharsets.UTF_8); TableFormatter formatter = SensitivityResultCsvWriter.createTableFormatter(writer); - TableFormatter formatterStatus = SensitivityResultCsvWriter.createContingencyStatusTableFormatter(writerStatuses)) { - SensitivityResultWriter valuesWriter = new SensitivityResultCsvWriter(formatter, formatterStatus, parametersRecord.contingencies); + TableFormatter formatterComponentsStatus = SensitivityResultCsvWriter.createContingencyStatusComponentsTableFormatter(writerComponentsStatuses); + TableFormatter formatterPreContingency = SensitivityResultCsvWriter.createPreContingencyStatusTableFormatter(writerPreContingencyStatuses) + ) { + + SensitivityResultWriter valuesWriter = new SensitivityResultCsvWriter(formatter, formatterComponentsStatus, + formatterPreContingency, parametersRecord.contingencies); SensitivityAnalysis.run(parametersRecord.network, parametersRecord.network.getVariantManager().getWorkingVariantId(), parametersRecord.factorsReader, valuesWriter, parametersRecord.contingencies, parametersRecord.variableSets, parametersRecord.params, parametersRecord.computationManager, ReportNode.NO_OP); diff --git a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultCsvWriter.java b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultCsvWriter.java index 1017f126596..2ae2bc52c75 100644 --- a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultCsvWriter.java +++ b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultCsvWriter.java @@ -23,14 +23,17 @@ public class SensitivityResultCsvWriter implements SensitivityResultWriter { private final TableFormatter formatter; - private final TableFormatter formatterContingencyStatus; + private final TableFormatter formatterContingencyComponentsStatuses; + + private final TableFormatter formatterPreContingency; private final List contingencies; - public SensitivityResultCsvWriter(TableFormatter formatter, TableFormatter formatterContingencyStatus, - List contingencies) { + public SensitivityResultCsvWriter(TableFormatter formatter, TableFormatter formatterContingencyComponentsStatuses, + TableFormatter formatterPreContingency, List contingencies) { this.formatter = Objects.requireNonNull(formatter); - this.formatterContingencyStatus = Objects.requireNonNull(formatterContingencyStatus); + this.formatterContingencyComponentsStatuses = Objects.requireNonNull(formatterContingencyComponentsStatuses); + this.formatterPreContingency = Objects.requireNonNull(formatterPreContingency); this.contingencies = Objects.requireNonNull(contingencies); } @@ -45,13 +48,29 @@ public static TableFormatter createTableFormatter(Writer writer) { new Column("Sensitivity value")); } - public static TableFormatter createContingencyStatusTableFormatter(Writer writer) { + public static TableFormatter createContingencyStatusComponentsTableFormatter(Writer writer) { Objects.requireNonNull(writer); TableFormatterFactory factory = new CsvTableFormatterFactory(); var tfc = TableFormatterConfig.load(); return factory.create(writer, "Sensitivity analysis contingency status result", tfc, new Column("Contingency ID"), - new Column("Contingency Status")); + new Column("Contingency Status"), + new Column("Loadflow Status"), + new Column("Loadflow Status Description"), + new Column("Connected component"), + new Column("Synchronous component")); + } + + public static TableFormatter createPreContingencyStatusTableFormatter(Writer writer) { + Objects.requireNonNull(writer); + TableFormatterFactory factory = new CsvTableFormatterFactory(); + var tfc = TableFormatterConfig.load(); + return factory.create(writer, "Sensitivity analysis pre contingency status result", tfc, + new Column("Connected component"), + new Column("Synchronous component"), + new Column("Loadflow Status"), + new Column("Loadflow Status Description") + ); } @Override @@ -68,12 +87,33 @@ public void writeSensitivityValue(int factorIndex, int contingencyIndex, double } @Override - public void writeContingencyStatus(int contingencyIndex, SensitivityAnalysisResult.Status status) { + public void writeContingencyStatus(int contingencyIndex, SensitivityAnalysisResult.Status status, SensitivityAnalysisResult.LoadFlowStatus loadFlowStatus, int numCC, int numCS) { + try { + formatterContingencyComponentsStatuses.writeCell(contingencies.get(contingencyIndex).getId()); + formatterContingencyComponentsStatuses.writeCell(status.toString()); + formatterContingencyComponentsStatuses.writeCell(loadFlowStatus.status().toString()); + formatterContingencyComponentsStatuses.writeCell(loadFlowStatus.statusText()); + formatterContingencyComponentsStatuses.writeCell(numCC); + formatterContingencyComponentsStatuses.writeCell(numCS); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public void writeSynchronousComponentStatus(int numCC, int numCS, SensitivityAnalysisResult.LoadFlowStatus loadFlowStatus) { try { - formatterContingencyStatus.writeCell(contingencies.get(contingencyIndex).getId()); - formatterContingencyStatus.writeCell(status.name()); + formatterPreContingency.writeCell(numCC); + formatterPreContingency.writeCell(numCS); + formatterPreContingency.writeCell(loadFlowStatus.status().toString()); + formatterPreContingency.writeCell(loadFlowStatus.statusText()); } catch (IOException e) { throw new UncheckedIOException(e); } } + + @Override + public void computationComplete() { + // WHAT TO DO HERE ?? + } } diff --git a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultJsonWriter.java b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultJsonWriter.java index 61be3d216e5..2b85e5e1aa9 100644 --- a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultJsonWriter.java +++ b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultJsonWriter.java @@ -12,10 +12,7 @@ import java.io.IOException; import java.io.UncheckedIOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; +import java.util.*; /** * @author Geoffroy Jamgotchian {@literal } @@ -28,10 +25,15 @@ public class SensitivityResultJsonWriter implements SensitivityResultWriter, Aut private final List contingencyStatusBuffer; + private final List precontingencyStatusBuffer; + + private boolean computationComplete = false; + public SensitivityResultJsonWriter(JsonGenerator jsonGenerator, List contingencies) { this.jsonGenerator = Objects.requireNonNull(jsonGenerator); this.contingencies = Objects.requireNonNull(contingencies); this.contingencyStatusBuffer = new ArrayList<>(Collections.nCopies(contingencies.size(), null)); + this.precontingencyStatusBuffer = new ArrayList<>(); try { jsonGenerator.writeStartArray(); jsonGenerator.writeStartArray(); @@ -46,8 +48,21 @@ public void writeSensitivityValue(int factorIndex, int contingencyIndex, double } @Override - public void writeContingencyStatus(int contingencyIndex, SensitivityAnalysisResult.Status status) { + public void writeContingencyStatus(int contingencyIndex, SensitivityAnalysisResult.Status status, SensitivityAnalysisResult.LoadFlowStatus loadFlowStatus, int numCC, int numCs) { contingencyStatusBuffer.set(contingencyIndex, new SensitivityAnalysisResult.SensitivityContingencyStatus(contingencies.get(contingencyIndex).getId(), status)); + contingencyStatusBuffer.get(contingencyIndex).addComponentLoadFlowStatus(loadFlowStatus, numCC, numCs); + } + + @Override + public void writeSynchronousComponentStatus(int numCC, int numCS, SensitivityAnalysisResult.LoadFlowStatus loadFlowStatus) { + SensitivityAnalysisResult.SensitivityPreContingencyStatus e = new SensitivityAnalysisResult.SensitivityPreContingencyStatus(loadFlowStatus, numCC, numCS); + precontingencyStatusBuffer.add(e); + + } + + @Override + public void computationComplete() { + computationComplete = true; } @Override @@ -60,6 +75,17 @@ public void close() { SensitivityAnalysisResult.SensitivityContingencyStatus.writeJson(jsonGenerator, status); } jsonGenerator.writeEndArray(); + jsonGenerator.writeStartArray(); + for (SensitivityAnalysisResult.SensitivityPreContingencyStatus status : precontingencyStatusBuffer) { + SensitivityAnalysisResult.SensitivityPreContingencyStatus.writeJson(jsonGenerator, status); + } + jsonGenerator.writeEndArray(); + // Write computation completion information + jsonGenerator.writeStartArray(); + jsonGenerator.writeStartObject(); + jsonGenerator.writeBooleanField("completion", computationComplete); + jsonGenerator.writeEndObject(); + jsonGenerator.writeEndArray(); jsonGenerator.writeEndArray(); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultModelWriter.java b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultModelWriter.java index a15b16e9c2c..f1bb8817aed 100644 --- a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultModelWriter.java +++ b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultModelWriter.java @@ -25,9 +25,14 @@ public class SensitivityResultModelWriter implements SensitivityResultWriter { private final List contingencyStatuses; + private final List preContingencyStatuses; + + private boolean computationComplete; + public SensitivityResultModelWriter(List contingencies) { this.contingencies = Objects.requireNonNull(contingencies); contingencyStatuses = new ArrayList<>(Collections.nCopies(contingencies.size(), null)); + preContingencyStatuses = new ArrayList<>(); } public List getValues() { @@ -38,13 +43,36 @@ public List getContingen return contingencyStatuses; } + public List getPreContingencyStatuses() { + return preContingencyStatuses; + } + + public boolean isComputationComplete() { + return computationComplete; + } + @Override public void writeSensitivityValue(int factorIndex, int contingencyIndex, double value, double functionReference) { values.add(new SensitivityValue(factorIndex, contingencyIndex, value, functionReference)); } @Override - public void writeContingencyStatus(int contingencyIndex, SensitivityAnalysisResult.Status status) { - contingencyStatuses.set(contingencyIndex, new SensitivityAnalysisResult.SensitivityContingencyStatus(contingencies.get(contingencyIndex).getId(), status)); + public void writeContingencyStatus(int contingencyIndex, SensitivityAnalysisResult.Status status, + SensitivityAnalysisResult.LoadFlowStatus loadFlowStatus, int numCC, int numCs) { + if (contingencyStatuses.get(contingencyIndex) == null) { + contingencyStatuses.set(contingencyIndex, new SensitivityAnalysisResult.SensitivityContingencyStatus( + contingencies.get(contingencyIndex).getId(), status)); + } + contingencyStatuses.get(contingencyIndex).addComponentLoadFlowStatus(loadFlowStatus, numCC, numCs); + } + + @Override + public void writeSynchronousComponentStatus(int numCC, int numCS, SensitivityAnalysisResult.LoadFlowStatus loadFlowStatus) { + preContingencyStatuses.add(new SensitivityAnalysisResult.SensitivityPreContingencyStatus(loadFlowStatus, numCS, numCS)); + } + + @Override + public void computationComplete() { + computationComplete = true; } } diff --git a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultWriter.java b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultWriter.java index 25d010baf40..d3c2c0345b3 100644 --- a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultWriter.java +++ b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/SensitivityResultWriter.java @@ -9,10 +9,45 @@ /** * @author Geoffroy Jamgotchian {@literal } + * @author Fabrice Buscaylet {@literal sensitivityValues = null; List contingencyStatus = null; + List preContingencyStatus = new ArrayList<>(); List factors = null; while (parser.nextToken() != JsonToken.END_OBJECT) { switch (parser.currentName()) { @@ -55,6 +57,12 @@ public SensitivityAnalysisResult deserialize(JsonParser parser, DeserializationC parser.nextToken(); contingencyStatus = JsonUtil.readList(deserializationContext, parser, SensitivityAnalysisResult.SensitivityContingencyStatus.class); break; + + case "preContingencyStatus": + parser.nextToken(); + preContingencyStatus = JsonUtil.readList(deserializationContext, parser, SensitivityAnalysisResult.SensitivityPreContingencyStatus.class); + break; + default: throw new IllegalStateException("Unexpected field: " + parser.currentName()); } @@ -64,6 +72,6 @@ public SensitivityAnalysisResult deserialize(JsonParser parser, DeserializationC //Only 1.0 version is supported for now throw new IllegalStateException("Version different than 1.0 not supported."); } - return new SensitivityAnalysisResult(factors, contingencyStatus, sensitivityValues); + return new SensitivityAnalysisResult(factors, contingencyStatus, preContingencyStatus, sensitivityValues); } } diff --git a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/json/SensitivityAnalysisResultSerializer.java b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/json/SensitivityAnalysisResultSerializer.java index 20ba97f5154..bc89a4bde12 100644 --- a/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/json/SensitivityAnalysisResultSerializer.java +++ b/sensitivity-analysis-api/src/main/java/com/powsybl/sensitivity/json/SensitivityAnalysisResultSerializer.java @@ -32,6 +32,9 @@ public void serialize(SensitivityAnalysisResult result, JsonGenerator jsonGenera serializerProvider.defaultSerializeField("sensitivityFactors", result.getFactors(), jsonGenerator); serializerProvider.defaultSerializeField("sensitivityValues", result.getValues(), jsonGenerator); serializerProvider.defaultSerializeField("contingencyStatus", result.getContingencyStatuses(), jsonGenerator); + if (!result.getPreContingencyStatuses().isEmpty()) { + serializerProvider.defaultSerializeField("preContingencyStatus", result.getPreContingencyStatuses(), jsonGenerator); + } jsonGenerator.writeEndObject(); } } diff --git a/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisProviderMock.java b/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisProviderMock.java index c60905b089e..f1f31b98ea2 100644 --- a/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisProviderMock.java +++ b/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisProviderMock.java @@ -15,6 +15,7 @@ import com.powsybl.computation.ComputationManager; import com.powsybl.contingency.Contingency; import com.powsybl.iidm.network.Network; +import com.powsybl.loadflow.LoadFlowResult; import java.util.Collections; import java.util.List; @@ -57,6 +58,8 @@ public CompletableFuture run(Network network, String workingVariantId, Sen .orElseThrow(); resultWriter.writeSensitivityValue(factorIndex[0]++, contingencyIndex, 0.0, 0.0); break; + default: + throw new IllegalStateException("Unexpected value: " + contingencyContext.getContextType()); } if (reportNode != null) { reportNode.newReportNode() @@ -65,16 +68,37 @@ public CompletableFuture run(Network network, String workingVariantId, Sen .add(); } }); + resultWriter.writeSynchronousComponentStatus(0, 1, new SensitivityAnalysisResult.LoadFlowStatus(LoadFlowResult.ComponentResult.Status.CONVERGED, "testStatusText")); for (int contingencyIndex = 0; contingencyIndex < contingencies.size(); contingencyIndex++) { - resultWriter.writeContingencyStatus(contingencyIndex, SensitivityAnalysisResult.Status.SUCCESS); + int resultCase = contingencyIndex % 5; + switch (resultCase) { + case 0 -> + resultWriter.writeContingencyStatus(contingencyIndex, SensitivityAnalysisResult.Status.SUCCESS, + new SensitivityAnalysisResult.LoadFlowStatus(LoadFlowResult.ComponentResult.Status.CONVERGED, + ""), 0, 0); + case 1 -> + resultWriter.writeContingencyStatus(contingencyIndex, SensitivityAnalysisResult.Status.SUCCESS, + new SensitivityAnalysisResult.LoadFlowStatus(LoadFlowResult.ComponentResult.Status.NO_CALCULATION, + ""), 0, 0); + case 2 -> + resultWriter.writeContingencyStatus(contingencyIndex, SensitivityAnalysisResult.Status.NO_IMPACT, + new SensitivityAnalysisResult.LoadFlowStatus(LoadFlowResult.ComponentResult.Status.CONVERGED, + ""), 0, 0); + case 3 -> + resultWriter.writeContingencyStatus(contingencyIndex, SensitivityAnalysisResult.Status.FAILURE, + new SensitivityAnalysisResult.LoadFlowStatus(LoadFlowResult.ComponentResult.Status.MAX_ITERATION_REACHED, + ""), 0, 0); + case 4 -> + resultWriter.writeContingencyStatus(contingencyIndex, SensitivityAnalysisResult.Status.FAILURE, + new SensitivityAnalysisResult.LoadFlowStatus(LoadFlowResult.ComponentResult.Status.FAILED, + ""), 0, 0); + default -> throw new IllegalStateException("Unexpected value: " + resultCase); + } } Executor executor = computationManager.getExecutor(); if (executor != null) { - executor.execute(new Runnable() { - @Override - public void run() { - // Simulate some processing - } + executor.execute(() -> { + // Simulate some processing }); } if (reportNode != null) { @@ -95,6 +119,7 @@ public void run() { .withUntypedValue("size", variableSets.size()) .add(); } + resultWriter.computationComplete(); return CompletableFuture.completedFuture(null); } diff --git a/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisResultTest.java b/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisResultTest.java index 485110b9f8a..8c397e13508 100644 --- a/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisResultTest.java +++ b/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisResultTest.java @@ -12,11 +12,13 @@ import com.powsybl.commons.PowsyblException; import com.powsybl.commons.json.JsonUtil; import com.powsybl.contingency.*; +import com.powsybl.loadflow.LoadFlowResult; import com.powsybl.sensitivity.json.SensitivityJsonModule; import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -56,8 +58,13 @@ void testSide1() { contingencyStatus.add(new SensitivityAnalysisResult.SensitivityContingencyStatus("NHV1_NHV2_2", SensitivityAnalysisResult.Status.SUCCESS)); contingencyStatus.add(new SensitivityAnalysisResult.SensitivityContingencyStatus("NHV2_NHV3", SensitivityAnalysisResult.Status.NO_IMPACT)); + List preContingencyStatus = new ArrayList<>(); + preContingencyStatus.add(new SensitivityAnalysisResult.SensitivityPreContingencyStatus( + new SensitivityAnalysisResult.LoadFlowStatus(LoadFlowResult.ComponentResult.Status.CONVERGED, "testConvergence"), + 1, 1)); + List values = List.of(value1, value2, value3, value4, value5); - SensitivityAnalysisResult result = new SensitivityAnalysisResult(factors, contingencyStatus, values); + SensitivityAnalysisResult result = new SensitivityAnalysisResult(factors, contingencyStatus, preContingencyStatus, values); assertEquals(5, result.getValues().size()); assertEquals(2, result.getValues("NHV1_NHV2_2").size()); @@ -116,7 +123,7 @@ void testSide2() { List values = List.of(value1, value2, value3, value4, value5); List contingencyStatus = new ArrayList<>(); contingencies.forEach(c -> contingencyStatus.add(new SensitivityAnalysisResult.SensitivityContingencyStatus(c.getId(), SensitivityAnalysisResult.Status.SUCCESS))); - SensitivityAnalysisResult result = new SensitivityAnalysisResult(factors, contingencyStatus, values); + SensitivityAnalysisResult result = new SensitivityAnalysisResult(factors, contingencyStatus, Collections.emptyList(), values); assertEquals(5, result.getValues().size()); assertEquals(2, result.getValues("NHV1_NHV2_2").size()); @@ -171,7 +178,7 @@ void testSide3() { List values = List.of(value1, value2, value3, value4, value5); List contingencyStatus = new ArrayList<>(); contingencies.forEach(c -> contingencyStatus.add(new SensitivityAnalysisResult.SensitivityContingencyStatus(c.getId(), SensitivityAnalysisResult.Status.SUCCESS))); - SensitivityAnalysisResult result = new SensitivityAnalysisResult(factors, contingencyStatus, values); + SensitivityAnalysisResult result = new SensitivityAnalysisResult(factors, contingencyStatus, Collections.emptyList(), values); assertEquals(5, result.getValues().size()); assertEquals(2, result.getValues("NHV1_NHV2_2").size()); @@ -222,7 +229,7 @@ void testBusVoltage() { List values = List.of(value1, value2, value3, value4); List contingencyStatus = new ArrayList<>(); contingencies.forEach(c -> contingencyStatus.add(new SensitivityAnalysisResult.SensitivityContingencyStatus(c.getId(), SensitivityAnalysisResult.Status.SUCCESS))); - SensitivityAnalysisResult result = new SensitivityAnalysisResult(factors, contingencyStatus, values); + SensitivityAnalysisResult result = new SensitivityAnalysisResult(factors, contingencyStatus, Collections.emptyList(), values); assertEquals(4, result.getValues().size()); assertEquals(1, result.getValues("NHV1_NHV2_2").size()); @@ -283,7 +290,7 @@ void testSerializeDeserialize() throws IOException { List contingencies = List.of(new Contingency("NHV1_NHV2_2", new BranchContingency("NHV1_NHV2_2"))); List contingencyStatus = new ArrayList<>(); contingencies.forEach(c -> contingencyStatus.add(new SensitivityAnalysisResult.SensitivityContingencyStatus(c.getId(), SensitivityAnalysisResult.Status.SUCCESS))); - SensitivityAnalysisResult result = new SensitivityAnalysisResult(factors, contingencyStatus, values); + SensitivityAnalysisResult result = new SensitivityAnalysisResult(factors, contingencyStatus, Collections.emptyList(), values); ObjectMapper objectMapper = JsonUtil.createObjectMapper().registerModule(new SensitivityJsonModule()); roundTripTest(result, (result2, jsonFile) -> JsonUtil.writeJson(jsonFile, result, objectMapper), jsonFile -> JsonUtil.readJson(jsonFile, SensitivityAnalysisResult.class, objectMapper), "/SensitivityAnalysisResultRefV1.json"); diff --git a/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisTest.java b/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisTest.java index 9c8e58c0416..b1191dbf990 100644 --- a/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisTest.java +++ b/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisTest.java @@ -16,6 +16,7 @@ import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.VariantManagerConstants; import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.loadflow.LoadFlowResult; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -23,7 +24,7 @@ import java.util.Collections; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; /** * @author Joris Mancini {@literal } @@ -120,6 +121,7 @@ void testRunWithReaderAndWriter() { .setComputationManager(computationManager) .setReportNode(ReportNode.NO_OP)); assertEquals(1, resultWriter.getValues().size()); + assertTrue(resultWriter.isComputationComplete()); } @Test @@ -183,18 +185,62 @@ void testRunWithFluentParams() { .setVariableSets(sensitivityVariableSets) .setComputationManager(computationManager) .setReportNode(reportRoot) - .setContingencies(List.of(new Contingency("contingency1"))) + .setContingencies(List.of(new Contingency("contingency1"), new Contingency("contingency2"), + new Contingency("contingency3"), new Contingency("contingency4"), + new Contingency("contingency5"))) .setParameters(sensitivityParameters.setVoltageVoltageSensitivityValueThreshold(0.1d)); SensitivityAnalysisResult result = SensitivityAnalysis.find().run(network, "VariantId", List.of(factor), runParameters); assertEquals(5, reportRoot.getChildren().size()); assertEquals("Test sensitivity factor functionId = " + factor.getFunctionId(), reportRoot.getChildren().get(0).getMessage()); - assertEquals("Test sensititity analysis variant = VariantId", reportRoot.getChildren().get(1).getMessage()); - assertEquals("Test contingencies size = 1", reportRoot.getChildren().get(2).getMessage()); + assertEquals("Test sensitivity analysis variant = VariantId", reportRoot.getChildren().get(1).getMessage()); + assertEquals("Test contingencies size = 5", reportRoot.getChildren().get(2).getMessage()); assertEquals("Test sensitivity parameters voltageVoltageSensitivityValueThreshold = 0.1", reportRoot.getChildren().get(3).getMessage()); assertEquals("Test variable sets size = 1", reportRoot.getChildren().get(4).getMessage()); assertEquals(1, Mockito.mockingDetails(computationManager).getInvocations().size()); assertEquals(1, result.getValues().size()); + + int contingencyIndex = 0; + for (var contingencyStatus : result.getContingencyStatuses()) { + var componentLoadFlowStatus = contingencyStatus.getComponentsLoadFlowStatusList().iterator().next(); + int resultCase = contingencyIndex % 5; + switch (resultCase) { + case 0 : + assertEquals(SensitivityAnalysisResult.Status.SUCCESS, contingencyStatus.getStatus()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, componentLoadFlowStatus.getFirst().status()); + assertEquals(0, componentLoadFlowStatus.getSecond()); + assertEquals(0, componentLoadFlowStatus.getThird()); + break; + case 1 : + assertEquals(SensitivityAnalysisResult.Status.SUCCESS, contingencyStatus.getStatus()); + assertEquals(LoadFlowResult.ComponentResult.Status.NO_CALCULATION, componentLoadFlowStatus.getFirst().status()); + assertEquals(0, componentLoadFlowStatus.getSecond()); + assertEquals(0, componentLoadFlowStatus.getThird()); + break; + case 2 : + assertEquals(SensitivityAnalysisResult.Status.NO_IMPACT, contingencyStatus.getStatus()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, componentLoadFlowStatus.getFirst().status()); + assertEquals(0, componentLoadFlowStatus.getSecond()); + assertEquals(0, componentLoadFlowStatus.getThird()); + break; + case 3 : + assertEquals(SensitivityAnalysisResult.Status.FAILURE, contingencyStatus.getStatus()); + assertEquals(LoadFlowResult.ComponentResult.Status.MAX_ITERATION_REACHED, componentLoadFlowStatus.getFirst().status()); + assertEquals(0, componentLoadFlowStatus.getSecond()); + assertEquals(0, componentLoadFlowStatus.getThird()); + break; + case 4 : + assertEquals(SensitivityAnalysisResult.Status.FAILURE, contingencyStatus.getStatus()); + assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, componentLoadFlowStatus.getFirst().status()); + assertEquals(0, componentLoadFlowStatus.getSecond()); + assertEquals(0, componentLoadFlowStatus.getThird()); + break; + default : + fail(); + } + contingencyIndex++; + + } } @Test @@ -213,7 +259,7 @@ void testDefaultVariant() { SensitivityAnalysisResult result = SensitivityAnalysis.find().run(n, List.of(factor), runParameters); assertEquals(5, reportRoot.getChildren().size()); - assertEquals("Test sensititity analysis variant = Another variant", reportRoot.getChildren().get(1).getMessage()); + assertEquals("Test sensitivity analysis variant = Another variant", reportRoot.getChildren().get(1).getMessage()); assertEquals(1, Mockito.mockingDetails(computationManager).getInvocations().size()); assertEquals(1, result.getValues().size()); } diff --git a/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisToolTest.java b/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisToolTest.java index c411358e8e1..8af11d661eb 100644 --- a/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisToolTest.java +++ b/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityAnalysisToolTest.java @@ -17,6 +17,7 @@ import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; import com.powsybl.iidm.serde.NetworkSerDe; +import com.powsybl.loadflow.LoadFlowResult; import com.powsybl.sensitivity.json.SensitivityJsonModule; import com.powsybl.tools.test.AbstractToolTest; import com.powsybl.tools.Tool; @@ -112,19 +113,22 @@ void runJsonOutput() throws IOException { "--contingencies-file", "contingencies.json", "--variable-sets-file", "variableSets.json", "--parameters-file", "parameters.json", - "--output-file", "output.json"}, + "--output-file", "output.json", + }, expectedOut); assertTrue(Files.exists(fileSystem.getPath("output.json"))); List values; List status; + List statusPreContingencyStatus; try (Reader reader = Files.newBufferedReader(fileSystem.getPath("output.json"))) { List> lists = objectMapper.readValue(reader, new TypeReference<>() { }); values = objectMapper.convertValue(lists.get(0), new TypeReference<>() { }); status = objectMapper.convertValue(lists.get(1), new TypeReference<>() { }); + statusPreContingencyStatus = objectMapper.convertValue(lists.get(2), new TypeReference<>() { }); } assertEquals(2, values.size()); - SensitivityValue value0 = values.get(0); + SensitivityValue value0 = values.getFirst(); assertEquals(0, value0.getFactorIndex()); assertEquals(0, value0.getContingencyIndex()); SensitivityValue value1 = values.get(1); @@ -132,9 +136,14 @@ void runJsonOutput() throws IOException { assertEquals(0, value1.getContingencyIndex()); assertEquals(1, status.size()); - SensitivityAnalysisResult.SensitivityContingencyStatus status0 = status.get(0); + SensitivityAnalysisResult.SensitivityContingencyStatus status0 = status.getFirst(); assertEquals("NHV1_NHV2_2", status0.getContingencyId()); assertEquals(SensitivityAnalysisResult.Status.SUCCESS, status0.getStatus()); + + assertEquals(1, statusPreContingencyStatus.size()); + assertEquals("testStatusText", statusPreContingencyStatus.getFirst().getLoadFlowStatus().statusText()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, statusPreContingencyStatus.getFirst().getLoadFlowStatus().status()); + } @Test @@ -147,7 +156,9 @@ void runCsvOutput() throws IOException { "--contingencies-file", "contingencies.json", "--parameters-file", "parameters.json", "--output-file", "output.csv", - "--output-contingency-file", "outputContingency.csv"}, + "--output-contingency-file", "outputContingency.csv", + "--output-pre-contingency-file", "outputPreContingency.csv"}, + expectedOut); Path outputCsvFile = fileSystem.getPath("output.csv"); @@ -164,11 +175,20 @@ void runCsvOutput() throws IOException { assertTrue(Files.exists(outputContingencyStatusCsvFile)); String outputContingencyStatusCsvRef = TestUtil.normalizeLineSeparator(String.join(System.lineSeparator(), "Sensitivity analysis contingency status result", - "Contingency ID;Contingency Status", - "NHV1_NHV2_2;SUCCESS") - + System.lineSeparator()); + "Contingency ID;Contingency Status;Loadflow Status;Loadflow Status Description;Connected component;Synchronous component", + "NHV1_NHV2_2;SUCCESS;CONVERGED;;0;0") + + System.lineSeparator()); assertEquals(outputContingencyStatusCsvRef, TestUtil.normalizeLineSeparator(Files.readString(outputContingencyStatusCsvFile))); + Path outputCsvPreContingencyFile = fileSystem.getPath("outputPreContingency.csv"); + assertTrue(Files.exists(outputCsvPreContingencyFile)); + String outputPreContingencyReference = TestUtil.normalizeLineSeparator(String.join(System.lineSeparator(), + "Sensitivity analysis pre contingency status result", + "Connected component;Synchronous component;Loadflow Status;Loadflow Status Description", + "0;1;CONVERGED;testStatusText") + + System.lineSeparator()); + assertEquals(outputPreContingencyReference, TestUtil.normalizeLineSeparator(Files.readString(outputCsvPreContingencyFile))); + } @Test @@ -205,7 +225,7 @@ void checkThrowsWhenOutputFileAndContingencyDiffFormat() { "--factors-file", "factors.json", "--output-file", "output.csv", "--output-contingency-file", "outputContingency.json"}, - "output-file and output-contingency-file files must have the same format (csv)."); + "output-file and output-contingency-file and output-pre-contingency-file files must have the same format (csv)."); } @Test @@ -254,10 +274,16 @@ void runCommandWithSingleOutput() throws IOException { assertEquals(0, value1.getContingencyIndex()); assertEquals(1, result.getContingencyStatuses().size()); - SensitivityAnalysisResult.SensitivityContingencyStatus status0 = result.getContingencyStatuses().get(0); + SensitivityAnalysisResult.SensitivityContingencyStatus status0 = result.getContingencyStatuses().getFirst(); assertEquals("NHV1_NHV2_2", status0.getContingencyId()); assertEquals(SensitivityAnalysisResult.Status.SUCCESS, status0.getStatus()); + SensitivityAnalysisResult.SensitivityPreContingencyStatus prestatus0 = result.getPreContingencyStatuses().getFirst(); + assertEquals(1, prestatus0.getNumCC()); + assertEquals(1, prestatus0.getNumCS()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, prestatus0.getLoadFlowStatus().status()); + assertEquals("testStatusText", prestatus0.getLoadFlowStatus().statusText()); + assertEquals(2, result.getFactors().size()); SensitivityFactor factor0 = result.getFactors().get(0); assertEquals("NHV1_NHV2_1", factor0.getFunctionId()); diff --git a/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityContingencyStatusTest.java b/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityContingencyStatusTest.java new file mode 100644 index 00000000000..543283c07c7 --- /dev/null +++ b/sensitivity-analysis-api/src/test/java/com/powsybl/sensitivity/SensitivityContingencyStatusTest.java @@ -0,0 +1,89 @@ +package com.powsybl.sensitivity; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.powsybl.loadflow.LoadFlowResult; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SensitivityContingencyStatusTest { + + private final JsonFactory factory = new JsonFactory(); + + @Test + void parseJsonSensitivityContingencyStatus1() throws Exception { + // Arrange + String json = "{\"contingencyId\": \"ID_001\"}"; + JsonParser parser = factory.createParser(json); + parser.nextToken(); + parser.nextToken(); + + SensitivityAnalysisResult.SensitivityContingencyStatus.ParsingContext context = new SensitivityAnalysisResult.SensitivityContingencyStatus.ParsingContext(); + + // Act + SensitivityAnalysisResult.SensitivityContingencyStatus.parseJson(parser, context); + + // Assert + assertEquals("ID_001", context.contingency.getId()); + } + + @Test + void parseJsonSensitivityContingencyStatus2() throws Exception { + // Arrange + String json = """ + { + "componentsLoadFlowStatuses": [ + { + "loadFlowStatus": "CONVERGED", + "loadFlowStatusDescription": "TestConvergence", + "numCC": 5, + "numCS": 2 + } + ] + } + """; + JsonParser parser = factory.createParser(json); + parser.nextToken(); + parser.nextToken(); + + SensitivityAnalysisResult.SensitivityContingencyStatus.ParsingContext context = new SensitivityAnalysisResult.SensitivityContingencyStatus.ParsingContext(); + + // Act + SensitivityAnalysisResult.SensitivityContingencyStatus.parseJson(parser, context); + + // Assert + assertEquals(1, context.componentLoadflowStatuses.size()); + var triple = context.componentLoadflowStatuses.getFirst(); + + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, triple.getFirst().status()); + assertEquals("TestConvergence", triple.getFirst().statusText()); + assertEquals(5, triple.getSecond()); + assertEquals(2, triple.getThird()); + } + + @Test + void parseJsonSensitivityPreContingencyStatus() throws Exception { + // Arrange + String json = """ + [{ + "loadFlowStatus": { "status": "CONVERGED", "statusText": "TestStatusText" }, + "numCC": 5, + "numCS": 2 + }] + """; + JsonParser parser = factory.createParser(json); + + SensitivityAnalysisResult.SensitivityPreContingencyStatus.ParsingContext context = new SensitivityAnalysisResult.SensitivityPreContingencyStatus.ParsingContext(); + + SensitivityAnalysisResult.SensitivityPreContingencyStatus.parseJson(parser, context); + + assertEquals(1, context.preContingencyloadflowStatuses.size()); + SensitivityAnalysisResult.SensitivityPreContingencyStatus sensitivityPreContingencyStatus = context.preContingencyloadflowStatuses.getFirst(); + + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, sensitivityPreContingencyStatus.getLoadFlowStatus().status()); + assertEquals("TestStatusText", sensitivityPreContingencyStatus.getLoadFlowStatus().statusText()); + assertEquals(5, sensitivityPreContingencyStatus.getNumCC()); + assertEquals(2, sensitivityPreContingencyStatus.getNumCS()); + + } +} diff --git a/sensitivity-analysis-api/src/test/resources/i18n/reports.properties b/sensitivity-analysis-api/src/test/resources/i18n/reports.properties index cf4b0a1fa3e..6c3fde80947 100644 --- a/sensitivity-analysis-api/src/test/resources/i18n/reports.properties +++ b/sensitivity-analysis-api/src/test/resources/i18n/reports.properties @@ -1,4 +1,4 @@ -testSensitivityAnalysis = Test sensititity analysis variant = ${variantId} +testSensitivityAnalysis = Test sensitivity analysis variant = ${variantId} testContingenciesList = Test contingencies size = ${size} testSensitivityParameters = Test sensitivity parameters voltageVoltageSensitivityValueThreshold = ${threshold} testSensitivityFactors = Test sensitivity factor functionId = ${functionId}