Write the Java code to perform power flows

This tutorial shows you how to write a Java code to perform load flow calculations on a network, just on its current state but also on an N-1 state by applying a contingencies. You’ll see how to configure PowSyBl, through a YML file and overwriting it with a JSON file, how to provide the input network, and how to output the load flow results to the terminal.

What will you build?

The tutorial can be expressed in a short and easy workflow: all the input data is stored in an XIIDM file. This file is imported with the IIDM importer. Then, a load flow simulator is launched to get flows on all nodes. In this tutorial, the simulator is Open Loadflow, but it could be another load flow simulator, as long as the API contract is respected. A contingency is created and finally, the flows are computed again in order to get the final state.

Workflow

What will you need?

  • About 1/2 hour
  • A favorite text editor or IDE
  • JDK 1.17 or later
  • You can also import the code straight into your IDE:

How to complete this tutorial?

Like most tutorials, you can start from scratch and complete each step, or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.

To start from scratch, move on to Create a new project.

To skip the basics, do the following:

  • Download and unzip the source repository, or clone it using Git: git clone https://github.com/powsybl/powsybl-tutorials.
  • Change directory to loadflow/initial
  • Jump ahead to Configure the pom file

When you finish, you can check your results against the code in loadflow/complete.

Create a new project from scratch

Create a new Maven’s pom.xml file in loadflow with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.powsybl</groupId>
        <artifactId>powsybl-parent</artifactId>
        <version>8</version>
        <relativePath/>
    </parent>

    <artifactId>powsybl-loadflow</artifactId>

    <properties>
        <maven.exec.version>1.6.0</maven.exec.version>
        <powsybl-dependencies.version>2023.3.1</powsybl-dependencies.version>
    </properties>
</project>

Configure the maven pom file

First, in the pom.xml, add the following lines in the <properties> section to make it possible to run the future main class through maven:

<exec.cleanupDaemonThreads>false</exec.cleanupDaemonThreads>
<exec.mainClass>powsybl.tutorials.loadflow.LoadflowTutorial</exec.mainClass>

When you’ll have created the LoadflowTutorial class and its main function, you’ll then be able to execute your code through:

$> mvn clean package exec:exec

Also, configure the pom file to use a configuration file taken in the classpath, instead of the one that is global to your system:

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.6.0</version>
            <configuration>
                <systemProperties>
                    <systemProperty>
                        <key>powsybl.config.dirs</key>
                        <value>${project.build.directory}/classes</value>
                    </systemProperty>
                </systemProperties>
            </configuration>
        </plugin>
    </plugins>
</build>

Now, we’ll add a few required maven dependencies:

  • com.powsybl:powsybl-config-classic: to provide a way to read the configuration
  • org.slf4j:slf4j-simple: to provide an implementation of slf4j.
  • com.powsybl:powsybl-open-loadflow to provide an implementation for the loadflow calculation.
  • com.powsybl:powsybl-iidm-impl to load and work with networks.

Note: PowSyBl uses slf4j as a facade for various logging framework, but some APIs we use in PowSyBl use log4j, which is not compatible with slf4j, making it necessary to create a bridge between the two logging system.

Add the following dependencies to the pom.xml file:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.powsybl</groupId>
      <artifactId>powsybl-dependencies</artifactId>
      <version>${powsybl-dependencies.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
            <groupId>com.powsybl</groupId>
            <artifactId>powsybl-config-classic</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>${slf4j.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.powsybl</groupId>
            <artifactId>powsybl-open-loadflow</artifactId>
        </dependency>
        <dependency>
            <groupId>com.powsybl</groupId>
            <artifactId>powsybl-iidm-impl</artifactId>
        </dependency>
</dependencies>

Configure PowSyBl

We have configured this tutorial to use a locally defined config.yml file. Edit the file named config.yml at the location loadflow/src/main/resources. Start the configuration by writing:

load-flow:
  default-impl-name: "OpenLoadFlow"

In this way, PowSyBl will be set to use the OpenLoadflow implementation for the power flow.

Import the network from an XML IIDM file

In this tutorial, the network is quite simple and made of two lines in parallel, with a generator on the left side and a load on the right side. The load consumes 600 MW and the generator produces 606.5 MW.

Initial simple network

First, create a logger outside your main method. We will use it to display information about the objects we handle.

private static final Logger LOG = LoggerFactory.getLogger(LoadflowTutorial.class);

The network is modeled in IIDM, which is the internal model of Powsybl. This model can be serialized in an XML format for experimental purposes.

final String networkFileName = "eurostag-tutorial1-lf.xml";
final InputStream is = LoadflowTutorial.class.getClassLoader().getResourceAsStream(networkFileName);


The file is imported through a gateway that converts the file to an in-memory model.

Network network = Network.read(networkFileName, is);


Let’s just quickly scan the network. In this tutorial it is composed of two substations. Each substation has two voltage levels and one two-windings transformer.

for (Substation substation : network.getSubstations()) {
    LOGGER.info("Substation {}", substation.getNameOrId());
    LOGGER.info("Voltage levels:");
    for (VoltageLevel voltageLevel : substation.getVoltageLevels()) {
        LOGGER.info("Voltage level: {} {}kV", voltageLevel.getId(), voltageLevel.getNominalV());
    }
    LOGGER.info("Two windings transformers:");
    for (TwoWindingsTransformer t2wt : substation.getTwoWindingsTransformers()) {
        LOGGER.info("Two winding transformer: {}", t2wt.getNameOrId());
    }
    LOGGER.info("Three windings transformers:");
    for (ThreeWindingsTransformer t3wt : substation.getThreeWindingsTransformers()) {
        LOGGER.info("Three winding transformer: {}", t3wt.getNameOrId());
    }
}

There are two lines in the network.

for (Line line : network.getLines()) {
    LOGGER.info("Line: {}", line.getNameOrId());
    LOGGER.info(" > Terminal 1 power: {}", line.getTerminal1().getP());
    LOGGER.info(" > Terminal 2 power: {}", line.getTerminal2().getP());
}

Run a power flow calculation

Then, flows are computed with a load flow simulator. In this tutorial, we use the OpenLoadflow implementation, which is open-source software, natilevly based on the Powsybl network grid model. For more details, please visit the documentation to learn more about it.

A loadflow is run on a variant of the network. A network variant is close to a state vector and gathers variables such as injections, productions, tap positions, states of buses, etc. The computed flows are stored in the variant given in input. Defining the variant specifically is actually optional. If it is not the case, the computation will be run on the default initial variant created by PowSyBl by default. Let us first define the variant:

final String variantId = "loadflowVariant";
network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, variantId);
network.getVariantManager().setWorkingVariant(variantId);

Here we have saved the initial variant and set the new variant as the one to be used.

In order to run the load flow calculation, we also need to define the set of parameters to be used. The default parameters are listed here. Here, angles are set to zero and voltages are set to one per unit.

LoadFlowParameters loadflowParameters = new LoadFlowParameters()
        .setVoltageInitMode(LoadFlowParameters.VoltageInitMode.UNIFORM_VALUES);
LoadFlow.run(network, loadflowParameters);

The flow through the upper line is of 302.4 MW at its entrance and of 300.4 MW at its exit. The flow through the lower line is the same. The power losses are of 2 MW on each line.

If you wish to set the parameters in the config file and use them directly, you can write instead:

LoadFlowParameters loadflowParameters = LoadFlowParameters.load();
LoadFlow.run(network, loadflowParameters);

You’ll have to fill two configuration sections in the config.yml file, for example:

load-flow-default-parameters:
  voltageInitMode: DC_VALUES
  transformerVoltageControlOn: false
  twtSplitShuntAdmittance: true
  dc: false

open-loadflow-default-parameters:
  lowImpedanceBranchMode: REPLACE_BY_ZERO_IMPEDANCE_LINE

Output the results in the terminal

Let us compare the voltages and angles before and after the calculation:

double angle;
double v;
double oldAngle;
double oldV;
for (Bus bus : network.getBusView().getBuses()) {
    network.getVariantManager().setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID);
    oldAngle = bus.getAngle();
    oldV = bus.getV();
    network.getVariantManager().setWorkingVariant(variantId);
    angle = bus.getAngle();
    v = bus.getV();
    LOGGER.info("Angle difference  : {}", angle - oldAngle);
    LOGGER.info("Tension difference: {}", v - oldV);
}

Apply a contingency on the network and run a load flow again

Final simple network


A contingency is simply simulated by disconnecting both terminals of the NHV1_NHV2_1 line.

network.getLine("NHV1_NHV2_1").getTerminal1().disconnect();
network.getLine("NHV1_NHV2_1").getTerminal2().disconnect();


Once the contingency is applied on the network, the post-contingency state of the network is computed through a loadflow in the same way as above.

A new load flow computes the flow on the lower line: it is now of 610.6 MW at its entrance and of 601 MW at its exit. The rest of the difference between load and generation represents the losses during the voltage transformation process.

final String contingencyVariantId = "contingencyLoadflowVariant";
network.getVariantManager().cloneVariant(variantId, contingencyVariantId);
network.getVariantManager().setWorkingVariant(contingencyVariantId);
LoadFlow.run(network);

Let’s analyze the results. First we make some simple prints in the terminal:

for (Line l : network.getLines()) {
    LOGGER.info("Line: {}", line.getNameOrId());
    LOGGER.info(" > Terminal 1 power: {}", line.getTerminal1().getP());
    LOGGER.info(" > Terminal 2 power: {}", line.getTerminal2().getP());
}

Here we will also show how to define a visitor object, that may be used to loop over equipments. We will use it to print the energy sources and the loads of the network. Visitors are usually used to access the network equipments efficiently, and modify their properties for instance. Here we just print some data about generators and loads.

final TopologyVisitor visitor = new DefaultTopologyVisitor() {
    @Override
    public void visitGenerator(Generator generator) {
        LOGGER.info("Generator: {} [{} MW]", generator.getNameOrId(), generator.getTerminal().getP());
    }

    @Override
    public void visitLoad(Load load) {
        LOGGER.info("Load: {} [{} MW]", load.getNameOrId(), load.getTerminal().getP());
    }
};
for (VoltageLevel voltageLevel : network.getVoltageLevels()) {
    voltageLevel.visitEquipments(visitor);
}

The power now flows only through the line NHV1_NHV2_2, as expected.

Summary

We have learnt how to write Java code to run power flows. We have shown how to load a network file, how to create and use network variants, and how to set the load flow parameters. We’ve also seen how to output the results in the terminal.

Going further

The following links could also be useful: