Skip to content

Commit 75f191e

Browse files
committed
feat(loadflow-validation): add private extensions validation
Signed-off-by: Samir Romdhani <samir.romdhani_externe@rte-france.com> add ExtensionsValidation tests Signed-off-by: Samir Romdhani <samir.romdhani_externe@rte-france.com> WIP Signed-off-by: Samir Romdhani <samir.romdhani_externe@rte-france.com>
1 parent d6a6ea0 commit 75f191e

File tree

11 files changed

+341
-3
lines changed

11 files changed

+341
-3
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
https://powsybl.readthedocs.io/projects/powsybl-core/en/stable/user/itools/loadflow-validation.html
2+
3+
## Flows (BranchData) can be constructed from
4+
- Line Flows
5+
- TwoWindingsTransformer Flows
6+
- Used Extensions
7+
- TwoWindingsTransformerPhaseAngleClock
8+
- phaseAngleClock
9+
- TieLine Flows
10+
11+
## ThreeWindingsTransformer
12+
- Used Extensions
13+
- ThreeWindingsTransformerPhaseAngleClock
14+
- phaseAngleClock2
15+
- phaseAngleClock3
16+
17+
```sh
18+
./distribution-core/target/powsybl/bin/itools loadflow-validation \
19+
--case-file ./distribution-core/test/network.xiidm \
20+
--output-folder /tmp/lv-out \
21+
--load-flow \
22+
--verbose
23+
```

loadflow/loadflow-validation/pom.xml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,10 @@
9999
<groupId>org.slf4j</groupId>
100100
<artifactId>slf4j-simple</artifactId>
101101
</dependency>
102+
<dependency>
103+
<groupId>com.powsybl</groupId>
104+
<artifactId>powsybl-tools-test</artifactId>
105+
<scope>test</scope>
106+
</dependency>
102107
</dependencies>
103-
104108
</project>
105-

loadflow/loadflow-validation/src/main/java/com/powsybl/loadflow/validation/ValidationTool.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.powsybl.iidm.network.tools.ConversionToolUtils;
1818
import com.powsybl.loadflow.LoadFlow;
1919
import com.powsybl.loadflow.LoadFlowParameters;
20+
import com.powsybl.loadflow.validation.extension.ExtensionsValidation;
2021
import com.powsybl.loadflow.validation.io.ValidationWriters;
2122
import com.powsybl.tools.Command;
2223
import com.powsybl.tools.Tool;
@@ -53,6 +54,7 @@ public class ValidationTool implements Tool {
5354
private static final String COMPARE_RESULTS = "compare-results";
5455
private static final String RUN_COMPUTATION = "run-computation";
5556
private static final String COMPARE_CASE_FILE = "compare-case-file";
57+
public static final String WITH_EXTENSIONS_OPTION = "with-extensions";
5658

5759
private static final Command COMMAND = new Command() {
5860

@@ -120,6 +122,11 @@ public Options getOptions() {
120122
.build());
121123
options.addOption(createImportParametersFileOption());
122124
options.addOption(createImportParameterOption());
125+
options.addOption(Option.builder().longOpt(WITH_EXTENSIONS_OPTION)
126+
.desc("enable extension validation")
127+
.hasArg(false)
128+
.argName("EXTENSIONS")
129+
.build());
123130
return options;
124131
}
125132

@@ -149,6 +156,7 @@ public void run(CommandLine line, ToolRunningContext context) throws Exception {
149156
if (line.hasOption(OUTPUT_FORMAT)) {
150157
config.setValidationOutputWriter(ValidationOutputWriter.valueOf(line.getOptionValue(OUTPUT_FORMAT)));
151158
}
159+
152160
ComparisonType comparisonType = null;
153161
if (line.hasOption(COMPARE_RESULTS)) {
154162
config.setCompareResults(true);
@@ -161,6 +169,10 @@ public void run(CommandLine line, ToolRunningContext context) throws Exception {
161169
.collect(Collectors.toSet());
162170
}
163171
Network network = loadNetwork(caseFile, line, context);
172+
if (line.hasOption(WITH_EXTENSIONS_OPTION)) {
173+
ExtensionsValidation extensionsValidation = new ExtensionsValidation();
174+
extensionsValidation.runExtensionValidations(network, config, outputFolder, context);
175+
}
164176
try (ValidationWriters validationWriters = new ValidationWriters(network.getId(), validationTypes, outputFolder, config)) {
165177
if (config.isCompareResults() && ComparisonType.COMPUTATION.equals(comparisonType)) {
166178
Preconditions.checkArgument(line.hasOption(LOAD_FLOW) || line.hasOption(RUN_COMPUTATION),
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright (c) 2026, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
* SPDX-License-Identifier: MPL-2.0
7+
*/
8+
package com.powsybl.loadflow.validation.extension; import com.powsybl.iidm.network.Network;
9+
import com.powsybl.loadflow.validation.ValidationConfig;
10+
11+
import java.io.IOException;
12+
import java.nio.file.Path;
13+
import java.util.Objects;
14+
15+
/**
16+
*
17+
* @author Samir Romdhani {@literal <samir.romdhani at rte-france.com>}
18+
*/
19+
public interface ExtensionValidation {
20+
21+
String getType();
22+
23+
String getName();
24+
25+
boolean check(Network network, ValidationConfig config, Path outputFile) throws IOException;
26+
27+
default Path getOutputFile(Path folder) {
28+
Objects.requireNonNull(folder);
29+
return folder.resolve(getName() + ".csv");
30+
}
31+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright (c) 2026, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
* SPDX-License-Identifier: MPL-2.0
7+
*/
8+
package com.powsybl.loadflow.validation.extension;
9+
10+
import com.google.common.base.Supplier;
11+
import com.google.common.base.Suppliers;
12+
import com.google.common.collect.Lists;
13+
import com.powsybl.iidm.network.Network;
14+
import com.powsybl.loadflow.validation.ValidationConfig;
15+
import com.powsybl.tools.ToolRunningContext;
16+
17+
import java.io.IOException;
18+
import java.nio.file.Path;
19+
import java.util.List;
20+
import java.util.Optional;
21+
import java.util.ServiceLoader;
22+
23+
/**
24+
* @author Samir Romdhani {@literal <samir.romdhani at rte-france.com>}
25+
*/
26+
public class ExtensionsValidation {
27+
28+
private static final Supplier<List<ExtensionValidation>> EXTENSIONS = Suppliers.memoize(() -> Lists.newArrayList(
29+
ServiceLoader.load(ExtensionValidation.class, ExtensionsValidation.class.getClassLoader())));
30+
31+
public static List<ExtensionValidation> getExtensions() {
32+
return EXTENSIONS.get();
33+
}
34+
35+
public static List<String> getExtensionsNames() {
36+
return getExtensions().stream().map(ExtensionValidation::getName).toList();
37+
}
38+
39+
public static Optional<ExtensionValidation> getExtension(String name) {
40+
return getExtensions().stream().filter(v -> v.getName().equals(name)).findFirst();
41+
}
42+
43+
public void runExtensionValidations(Network network, ValidationConfig config, Path outputFolder, ToolRunningContext context) {
44+
45+
getExtensions().forEach(extensionValidation -> {
46+
try {
47+
boolean success = extensionValidation.check(network, config, extensionValidation.getOutputFile(outputFolder));
48+
String message = "Validate extension behaviour of network " + network.getId()
49+
+ " - extension validation type: " + extensionValidation.getType()
50+
+ " - result: " + (success ? "success" : "fail");
51+
context.getOutputStream().println(message);
52+
} catch (IOException e) {
53+
throw new java.io.UncheckedIOException(e);
54+
}
55+
});
56+
}
57+
}

loadflow/loadflow-validation/src/test/java/com/powsybl/loadflow/validation/ValidationConfigTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ void checkCompleteConfig() throws Exception {
9999
}
100100

101101
@Test
102-
void checkSetters() throws Exception {
102+
void checkSetters() {
103103
ValidationConfig config = ValidationConfig.load(platformConfig);
104104
config.setThreshold(threshold);
105105
config.setVerbose(verbose);
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Copyright (c) 2026, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
* SPDX-License-Identifier: MPL-2.0
7+
*/
8+
package com.powsybl.loadflow.validation;
9+
10+
import com.powsybl.tools.Tool;
11+
import com.powsybl.tools.test.AbstractToolTest;
12+
import org.junit.jupiter.api.BeforeEach;
13+
import org.junit.jupiter.api.Disabled;
14+
import org.junit.jupiter.api.Test;
15+
16+
import java.io.IOException;
17+
import java.nio.file.Files;
18+
import java.util.Collections;
19+
import java.util.Objects;
20+
21+
import static org.junit.jupiter.api.Assertions.*;
22+
23+
/**
24+
*
25+
* @author Samir Romdhani {@literal <samir.romdhani at rte-france.com>}
26+
*/
27+
class ValidationToolTest extends AbstractToolTest {
28+
29+
private static final String COMMAND_NAME = "loadflow-validation";
30+
private final ValidationTool tool = new ValidationTool();
31+
32+
@BeforeEach
33+
public void setUp() throws Exception {
34+
super.setUp();
35+
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/network.xiidm")), fileSystem.getPath("network.xiidm"));
36+
}
37+
38+
@Override
39+
protected Iterable<Tool> getTools() {
40+
return Collections.singleton(tool);
41+
}
42+
43+
@Override
44+
public void assertCommand() {
45+
assertEquals(COMMAND_NAME, tool.getCommand().getName());
46+
assertEquals("Computation", tool.getCommand().getTheme());
47+
assertEquals("Validate load-flow results of a network", tool.getCommand().getDescription());
48+
49+
assertCommand(tool.getCommand(), COMMAND_NAME, 12, 2);
50+
assertOption(tool.getCommand().getOptions(), "case-file", true, true);
51+
assertOption(tool.getCommand().getOptions(), "output-folder", true, true);
52+
assertOption(tool.getCommand().getOptions(), "load-flow", false, false);
53+
assertOption(tool.getCommand().getOptions(), "types", false, true);
54+
assertOption(tool.getCommand().getOptions(), "with-extensions", false, false);
55+
}
56+
57+
@Test
58+
void testCommand() {
59+
assertCommand();
60+
}
61+
62+
@Test
63+
@Disabled
64+
void test() throws IOException {
65+
//TODO File network.xiidm does not exist !!!!!
66+
assertCommandErrorMatch(new String[]{"loadflow-validation", "--case-file", "network.xiidm", "--output-folder", "result"}, "");
67+
}
68+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright (c) 2026, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
* SPDX-License-Identifier: MPL-2.0
7+
*/
8+
package com.powsybl.loadflow.validation.extension; import com.google.auto.service.AutoService;
9+
import com.powsybl.iidm.network.Network;
10+
import com.powsybl.loadflow.validation.ValidationConfig;
11+
12+
import java.io.IOException;
13+
import java.nio.file.Path;
14+
15+
/**
16+
*
17+
* @author Samir Romdhani {@literal <samir.romdhani at rte-france.com>}
18+
*/
19+
@AutoService(ExtensionValidation.class)
20+
public class ExtensionValidationMock implements ExtensionValidation {
21+
22+
@Override
23+
public String getType() {
24+
return "private1";
25+
}
26+
27+
@Override
28+
public String getName() {
29+
return "extensionValidationMock1";
30+
}
31+
32+
@Override
33+
public boolean check(Network network, ValidationConfig config, Path outputFile) throws IOException {
34+
return true;
35+
}
36+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Copyright (c) 2026, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
* SPDX-License-Identifier: MPL-2.0
7+
*/
8+
package com.powsybl.loadflow.validation.extension;
9+
10+
import com.powsybl.computation.ComputationManager;
11+
import com.powsybl.iidm.network.Network;
12+
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
13+
import com.powsybl.loadflow.validation.ValidationConfig;
14+
import com.powsybl.tools.ToolRunningContext;
15+
import org.junit.jupiter.api.Test;
16+
import org.mockito.ArgumentCaptor;
17+
import org.mockito.MockedStatic;
18+
19+
import java.io.IOException;
20+
import java.io.PrintStream;
21+
import java.nio.file.FileSystem;
22+
import java.nio.file.Path;
23+
import java.util.List;
24+
25+
import static org.junit.jupiter.api.Assertions.*;
26+
import static org.mockito.ArgumentMatchers.any;
27+
import static org.mockito.Mockito.*;
28+
29+
/**
30+
*
31+
* @author Samir Romdhani {@literal <samir.romdhani at rte-france.com>}
32+
*/
33+
class ExtensionsValidationTest {
34+
35+
ExtensionsValidation extensionsValidation = new ExtensionsValidation();
36+
private final Network network = EurostagTutorialExample1Factory.create();
37+
private final Path outputFolder = mock(Path.class);
38+
39+
@Test
40+
void getExtensionsShouldSucceed() {
41+
assertEquals(1, ExtensionsValidation.getExtensions().size());
42+
}
43+
44+
@Test
45+
void getExtensionsNamesShouldSucceed() {
46+
assertEquals(List.of("extensionValidationMock1"), ExtensionsValidation.getExtensionsNames());
47+
}
48+
49+
@Test
50+
void getExtensionShouldReturnExistingExtension() {
51+
assertTrue(ExtensionsValidation.getExtension("extensionValidationMock1").isPresent());
52+
assertEquals("extensionValidationMock1", ExtensionsValidation.getExtension("extensionValidationMock1").get().getName());
53+
assertEquals("private1", ExtensionsValidation.getExtension("extensionValidationMock1").get().getType());
54+
when(outputFolder.resolve(anyString())).thenReturn(outputFolder);
55+
assertNotNull(ExtensionsValidation.getExtension("extensionValidationMock1").get().getOutputFile(outputFolder));
56+
}
57+
58+
@Test
59+
void testRunExtensionValidationsShouldSucceed() throws IOException {
60+
//Given
61+
ValidationConfig config = ValidationConfig.load();
62+
ToolRunningContext context = new ToolRunningContext(mock(PrintStream.class), mock(PrintStream.class), mock(FileSystem.class), mock(ComputationManager.class), mock(ComputationManager.class));
63+
64+
ExtensionValidation extension1 = mock(ExtensionValidation.class);
65+
ExtensionValidation extension2 = mock(ExtensionValidation.class);
66+
67+
when(extension1.getType()).thenReturn("type1");
68+
when(extension1.getOutputFile(outputFolder)).thenReturn(mock(Path.class));
69+
when(extension1.check(any(), any(), any())).thenReturn(true);
70+
71+
when(extension2.getType()).thenReturn("type2");
72+
when(extension2.getOutputFile(outputFolder)).thenReturn(mock(Path.class));
73+
when(extension2.check(any(), any(), any())).thenReturn(false);
74+
75+
try (MockedStatic<ExtensionsValidation> mocked = mockStatic(ExtensionsValidation.class)) {
76+
mocked.when(ExtensionsValidation::getExtensions).thenReturn(List.of(extension1, extension2));
77+
// When
78+
extensionsValidation.runExtensionValidations(network, config, outputFolder, context);
79+
// Then
80+
81+
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
82+
verify(context.getOutputStream(), times(2)).println(captor.capture());
83+
List<String> messages = captor.getAllValues();
84+
assertTrue(messages.get(0).contains("success"));
85+
assertTrue(messages.get(1).contains("fail"));
86+
87+
// check() was called on extensions
88+
verify(extension1, times(1)).check(eq(network), eq(config), any());
89+
verify(extension2, times(1)).check(eq(network), eq(config), any());
90+
}
91+
}
92+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
loadflow-validation:
2+
threshold: 0.1
3+
verbose: false
4+
load-flow-name: Mock
5+
table-formatter-factory: com.powsybl.commons.io.table.CsvTableFormatterFactory
6+
epsilon-x: 0.1
7+
apply-reactance-correction: false
8+
output-writer: CSV_MULTILINE
9+
ok-missing-values: false
10+
no-requirement-if-reactive-bound-inversion: false
11+
compare-results: false
12+
check-main-component-only: true
13+
no-requirement-if-setpoint-outside-power-bounds: false

0 commit comments

Comments
 (0)