LoadFlowAssert.java
/*
* Copyright (c) 2019-2025, RTE (http://www.rte-france.com)
* 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.util;
import com.google.common.io.ByteStreams;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.iidm.network.Bus;
import com.powsybl.iidm.network.Terminal;
import com.powsybl.loadflow.LoadFlowResult;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import static com.powsybl.commons.test.TestUtil.normalizeLineSeparator;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
public final class LoadFlowAssert {
public static final double DELTA_ANGLE = 1E-6d;
public static final double DELTA_V = 1E-2d;
public static final double DELTA_POWER = 1E-3d;
public static final double DELTA_RHO = 1E-6d;
public static final double DELTA_I = 1000 * DELTA_POWER / Math.sqrt(3);
public static final double DELTA_MISMATCH = 1E-4d;
public static final double DELTA_SENSITIVITY_VALUE = 1E-4d;
private LoadFlowAssert() {
}
public static void assertVoltageEquals(double v, Bus bus) {
assertEquals(v, bus.getV(), DELTA_V);
}
public static void assertAngleEquals(double a, Bus bus) {
assertEquals(a, bus.getAngle(), DELTA_ANGLE);
}
public static void assertActivePowerEquals(double p, Terminal terminal) {
assertEquals(p, terminal.getP(), DELTA_POWER);
}
public static void assertReactivePowerEquals(double q, Terminal terminal) {
assertEquals(q, terminal.getQ(), DELTA_POWER);
}
public static void assertCurrentEquals(double i, Terminal terminal) {
assertEquals(i, terminal.getI(), DELTA_I);
}
public static void assertUndefinedActivePower(Terminal terminal) {
assertTrue(Double.isNaN(terminal.getP()));
}
public static void assertUndefinedReactivePower(Terminal terminal) {
assertTrue(Double.isNaN(terminal.getQ()));
}
public static void assertLoadFlowResultsEquals(LoadFlowResult loadFlowResultExpected, LoadFlowResult loadFlowResult) {
assertEquals(loadFlowResultExpected.getStatus(), loadFlowResult.getStatus(),
"Wrong load flow status");
assertEquals(loadFlowResultExpected.getComponentResults().size(),
loadFlowResult.getComponentResults().size(),
"Wrong sub network count");
Iterator<LoadFlowResult.ComponentResult> componentResultIteratorExpected = loadFlowResultExpected.getComponentResults().iterator();
Iterator<LoadFlowResult.ComponentResult> componentResultIterator = loadFlowResult.getComponentResults().iterator();
// loop over components
while (componentResultIteratorExpected.hasNext()) {
LoadFlowResult.ComponentResult componentResultExpected = componentResultIteratorExpected.next();
LoadFlowResult.ComponentResult componentResult = componentResultIterator.next();
assertEquals(componentResultExpected.getStatus(),
componentResult.getStatus(),
"Wrong load flow result status");
assertEquals(componentResultExpected.getIterationCount(),
componentResult.getIterationCount(),
"Wrong iteration count");
assertEquals(componentResultExpected.getSlackBusResults().size(),
componentResult.getSlackBusResults().size(),
"Wrong slack bus results size");
if (!componentResult.getSlackBusResults().isEmpty()) {
assertEquals(componentResultExpected.getSlackBusResults().get(0).getActivePowerMismatch(),
componentResult.getSlackBusResults().get(0).getActivePowerMismatch(), DELTA_MISMATCH,
"Wrong active power mismatch");
}
}
}
public static void assertReportEquals(String refResourceName, ReportNode reportNode) throws IOException {
assertReportEquals(LoadFlowAssert.class.getResourceAsStream(refResourceName), reportNode);
}
private static String reportToString(ReportNode reportNode) throws IOException {
StringWriter sw = new StringWriter();
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator('.');
DecimalFormat decimalFormat = new DecimalFormat("#.######", symbols);
reportNode.print(sw, typedValue -> {
if (typedValue.getValue() instanceof Double) {
return decimalFormat.format(typedValue.getValue());
}
return typedValue.toString();
});
return sw.toString();
}
public static void assertReportEquals(InputStream ref, ReportNode reportNode) throws IOException {
String refLogExport = normalizeLineSeparator(new String(ByteStreams.toByteArray(ref), StandardCharsets.UTF_8));
String logExport = normalizeLineSeparator(reportToString(reportNode));
assertEquals(refLogExport, logExport);
}
public static void assertTxtReportEquals(String reportTxt, ReportNode reportNode) throws IOException {
String refLogExport = normalizeLineSeparator(reportTxt);
String logExport = normalizeLineSeparator(reportToString(reportNode));
assertEquals(refLogExport, logExport);
}
public static void assertReportEqualsString(String expected, ReportNode reportNode) throws IOException {
assertReportEquals(new ByteArrayInputStream(expected.getBytes()), reportNode);
}
public static Stream<ReportNode> streamReportNodes(final ReportNode node) {
return Stream.concat(Stream.of(node), node.getChildren().stream().flatMap(LoadFlowAssert::streamReportNodes));
}
public static void assertReportContains(String regex, ReportNode reportNode) {
List<ReportNode> matching = streamReportNodes(reportNode).filter(node -> node.getMessage().matches(regex)).toList();
assertFalse(matching.isEmpty(), () -> {
StringWriter sw = new StringWriter();
try {
reportNode.print(sw);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
String txtReport = normalizeLineSeparator(sw.toString());
return "Report does not contain '" + regex + "': \n-----\n" + txtReport;
});
}
}