DcDetailedNetworkFactory.java

/**
 * Copyright (c) 2025, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.iidm.network.test;

import com.powsybl.iidm.network.*;

import java.util.Map;
import java.util.Objects;

/**
 * @author Damien Jeandemange {@literal <damien.jeandemange at artelys.com>}
 */
public final class DcDetailedNetworkFactory {

    public static final String X_NODE_DC_1_FR = "xNodeDc1fr";
    public static final String X_NODE_DC_1_GB = "xNodeDc1gb";
    public static final String DC_NODE_FR_POS = "dcNodeFrPos";
    public static final String DC_NODE_FR_NEG = "dcNodeFrNeg";
    public static final String DC_NODE_GB_POS = "dcNodeGbPos";
    public static final String DC_NODE_GB_NEG = "dcNodeGbNeg";
    public static final String DC_GROUND_FR = "dcGroundFr";
    public static final String DC_GROUND_GB = "dcGroundGb";
    public static final String SUFFIX_NONE = "";
    public static final String SUFFIX_1 = "-1";
    public static final String SUFFIX_2 = "-2";
    public static final String SUFFIX_400 = "-400";
    public static final String SUFFIX_400_I = "-400-I";
    public static final String SUFFIX_150 = "-150";
    public static final String SUFFIX_150_1 = SUFFIX_150 + SUFFIX_1;
    public static final String SUFFIX_150_2 = SUFFIX_150 + SUFFIX_2;

    private DcDetailedNetworkFactory() {
    }

    public static String getVoltageLevelId(Country country, String xNode, String suffix) {
        return getId("VLDC-", country, xNode, suffix);
    }

    public static String getBusId(Country country, String xNode, String suffix) {
        return getId("BUSDC-", country, xNode, suffix);
    }

    public static String getTransformerId(Country country, String xNode, String suffix) {
        return getId("TRDC-", country, xNode, suffix);
    }

    public static String getLineId(Country country, String xNode, String suffix) {
        return getId("LINEDC-", country, xNode, suffix);
    }

    private static String getId(String type, Country country, String xNode, String suffix) {
        return type + country.name() + "-" + xNode + suffix;
    }

    /**
     * Creates a simple one bus AC (sub)network with dangling lines.
     * <br/>
     * Example with FR and one xNode where FR exports 200 MW:
     * <pre>
     *     var net = createSimpleAcNetworkWithDanglingLines(networkFactory, Country.FR, Map.of("xNode1", 200.));
     * </pre>
     *
     * <pre>
     *  targetP = 2000 MW / maxP = 4000 MW
     *  GEN-FR
     *    |
     *  (BUS-FR)-----(DLAC-FR-xNode1)  P0 = 200 MW
     *    |
     *  LOAD-FR
     *  P0 = 2000 MW - 200 MW = 1800 MW
     * </pre>
     */
    private static Network createSimpleAcNetworkWithDanglingLines(NetworkFactory networkFactory, Country country, Map<String, Double> xNodes) {
        Objects.requireNonNull(networkFactory);
        Network network = networkFactory.createNetwork(country.name(), "test");
        Substation s = network.newSubstation()
                .setId("S-" + country.name())
                .setCountry(country)
                .add();
        VoltageLevel vl = s.newVoltageLevel()
                .setId("VL-" + country.name())
                .setNominalV(400.0)
                .setLowVoltageLimit(380.0)
                .setHighVoltageLimit(420.0)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();
        Bus b = vl.getBusBreakerView().newBus()
                .setId("BUS-" + country.name())
                .add();
        vl.newGenerator()
                .setId("GEN-" + country.name())
                .setMinP(0.0)
                .setMaxP(4000.0)
                .setVoltageRegulatorOn(true)
                .setTargetV(400.0)
                .setTargetP(2000.0)
                .setTargetQ(0.0)
                .setBus(b.getId())
                .add();
        Load load = vl.newLoad()
                .setId("LOAD-" + country.name())
                .setP0(2000.0)
                .setQ0(0.0)
                .setBus(b.getId())
                .add();
        xNodes.forEach((xNode, v) -> {
            load.setP0(load.getP0() - v);
            vl.newDanglingLine()
                    .setId("DLAC-" + country.name() + "-" + xNode)
                    .setBus(b.getId())
                    .setR(0.3)
                    .setX(3.0)
                    .setB(0.0)
                    .setG(0.0)
                    .setP0(v)
                    .setQ0(0.0)
                    .setPairingKey(xNode)
                    .add();
        });
        return network;
    }

    enum Mode {
        ONE_T2WT,
        TWO_T2WT,
        T3WT
    }

    private static void addDcAcElements(Network network, Country country, String xNode, double exchange, Mode mode) {
        Objects.requireNonNull(network);
        Objects.requireNonNull(country);
        Objects.requireNonNull(xNode);
        Objects.requireNonNull(mode);
        Substation s = network.newSubstation()
                .setId("SDC-" + country.name() + "-" + xNode)
                .add();
        VoltageLevel vldc400 = s.newVoltageLevel()
                .setId(getVoltageLevelId(country, xNode, SUFFIX_400))
                .setNominalV(400.0)
                .setLowVoltageLimit(380.0)
                .setHighVoltageLimit(420.0)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();
        Bus bDc400 = vldc400.getBusBreakerView().newBus()
                .setId(getBusId(country, xNode, SUFFIX_400))
                .add();
        vldc400.newDanglingLine()
                .setId("DLDC-" + country.name() + "-" + xNode)
                .setBus(bDc400.getId())
                .setR(0.3)
                .setX(3.0)
                .setB(0.0)
                .setG(0.0)
                .setP0(exchange)
                .setQ0(0.0)
                .setPairingKey(xNode)
                .add();
        VoltageLevel vldc150 = s.newVoltageLevel()
                .setId(getVoltageLevelId(country, xNode, SUFFIX_150))
                .setNominalV(150.0)
                .setLowVoltageLimit(120.0)
                .setHighVoltageLimit(180.0)
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();
        if (mode == Mode.ONE_T2WT) {
            Bus bDc1501 = vldc150.getBusBreakerView().newBus()
                    .setId(getBusId(country, xNode, SUFFIX_150))
                    .add();
            s.newTwoWindingsTransformer()
                    .setId(getTransformerId(country, xNode, SUFFIX_NONE))
                    .setVoltageLevel1(vldc400.getId())
                    .setBus1(bDc400.getId())
                    .setConnectableBus1(bDc400.getId())
                    .setRatedU1(400.0)
                    .setVoltageLevel2(vldc150.getId())
                    .setBus2(bDc1501.getId())
                    .setConnectableBus2(bDc1501.getId())
                    .setRatedU2(150)
                    .setR(0.1)
                    .setX(5.0)
                    .setG(0.0)
                    .setB(0.0)
                    .add();
        } else if (mode == Mode.TWO_T2WT) {
            Bus bDc1501 = vldc150.getBusBreakerView().newBus()
                    .setId(getBusId(country, xNode, SUFFIX_150_1))
                    .add();
            Bus bDc1502 = vldc150.getBusBreakerView().newBus()
                    .setId(getBusId(country, xNode, SUFFIX_150_2))
                    .add();
            Bus bDc400i = vldc400.getBusBreakerView().newBus()
                    .setId(getBusId(country, xNode, SUFFIX_400_I))
                    .add();
            network.newLine()
                    .setId(getLineId(country, xNode, SUFFIX_400_I))
                    .setVoltageLevel1(vldc400.getId())
                    .setBus1(bDc400.getId())
                    .setConnectableBus1(bDc400.getId())
                    .setVoltageLevel2(vldc400.getId())
                    .setBus2(bDc400i.getId())
                    .setConnectableBus2(bDc400i.getId())
                    .setR(0.3)
                    .setX(3.0)
                    .setG1(0.0)
                    .setB1(0.0)
                    .setG2(0.0)
                    .setB2(0.0)
                    .add();
            s.newTwoWindingsTransformer()
                    .setId(getTransformerId(country, xNode, SUFFIX_1))
                    .setVoltageLevel1(vldc400.getId())
                    .setBus1(bDc400i.getId())
                    .setConnectableBus1(bDc400i.getId())
                    .setRatedU1(400.0)
                    .setVoltageLevel2(vldc150.getId())
                    .setBus2(bDc1501.getId())
                    .setConnectableBus2(bDc1501.getId())
                    .setRatedU2(150)
                    .setR(0.1)
                    .setX(5.0)
                    .setG(0.0)
                    .setB(0.0)
                    .add();
            s.newTwoWindingsTransformer()
                    .setId(getTransformerId(country, xNode, SUFFIX_2))
                    .setVoltageLevel1(vldc400.getId())
                    .setBus1(bDc400i.getId())
                    .setConnectableBus1(bDc400i.getId())
                    .setRatedU1(400.0)
                    .setVoltageLevel2(vldc150.getId())
                    .setBus2(bDc1502.getId())
                    .setConnectableBus2(bDc1502.getId())
                    .setRatedU2(150)
                    .setR(0.1)
                    .setX(5.0)
                    .setG(0.0)
                    .setB(0.0)
                    .add();

        } else if (mode == Mode.T3WT) {
            Bus bDc1501 = vldc150.getBusBreakerView().newBus()
                    .setId(getBusId(country, xNode, SUFFIX_150_1))
                    .add();
            Bus bDc1502 = vldc150.getBusBreakerView().newBus()
                    .setId(getBusId(country, xNode, SUFFIX_150_2))
                    .add();
            s.newThreeWindingsTransformer()
                    .setId(getTransformerId(country, xNode, SUFFIX_NONE))
                    .setRatedU0(400.0)
                    .newLeg1()
                    .setR(0.1)
                    .setX(0.0)
                    .setG(0.0)
                    .setB(0.0)
                    .setRatedU(400.0)
                    .setVoltageLevel(vldc400.getId())
                    .setBus(bDc400.getId())
                    .add()
                    .newLeg2()
                    .setR(0.1)
                    .setX(5.0)
                    .setG(0.0)
                    .setB(0.0)
                    .setRatedU(150.0)
                    .setVoltageLevel(vldc150.getId())
                    .setBus(bDc1501.getId())
                    .add()
                    .newLeg3()
                    .setR(0.1)
                    .setX(5.0)
                    .setG(0.0)
                    .setB(0.0)
                    .setRatedU(150.0)
                    .setVoltageLevel(vldc150.getId())
                    .setBus(bDc1502.getId())
                    .add()
                    .add();
        }
    }

    private static Network createLccMonopoleBase(NetworkFactory networkFactory, String dcNetworkId) {
        Objects.requireNonNull(networkFactory);
        Objects.requireNonNull(dcNetworkId);

        Network dcNetwork = networkFactory.createNetwork(dcNetworkId, "test");
        Network fr = createSimpleAcNetworkWithDanglingLines(networkFactory, Country.FR, Map.of(X_NODE_DC_1_FR, 200.));
        Network gb = createSimpleAcNetworkWithDanglingLines(networkFactory, Country.GB, Map.of(X_NODE_DC_1_GB, -200.));
        addDcAcElements(dcNetwork, Country.FR, X_NODE_DC_1_FR, -200., Mode.TWO_T2WT);
        addDcAcElements(dcNetwork, Country.GB, X_NODE_DC_1_GB, 200., Mode.T3WT);

        DcNode dcNodeFrPos = dcNetwork.newDcNode()
                .setId(DC_NODE_FR_POS)
                .setNominalV(500.)
                .add();
        DcNode dcNodeFrNeg = dcNetwork.newDcNode()
                .setId(DC_NODE_FR_NEG)
                .setNominalV(1.)
                .add();
        DcNode dcNodeGbPos = dcNetwork.newDcNode()
                .setId(DC_NODE_GB_POS)
                .setNominalV(500.)
                .add();
        DcNode dcNodeGbNeg = dcNetwork.newDcNode()
                .setId(DC_NODE_GB_NEG)
                .setNominalV(1.)
                .add();
        dcNetwork.newDcGround()
                .setId(DC_GROUND_FR)
                .setDcNode(dcNodeFrNeg.getId())
                .setConnected(true)
                .setR(0.0)
                .add();
        dcNetwork.newDcGround()
                .setId(DC_GROUND_GB)
                .setDcNode(dcNodeGbNeg.getId())
                .setConnected(true)
                .setR(0.0)
                .add();
        dcNetwork.newDcLine()
                .setId("dcLine1")
                .setDcNode1(dcNodeFrPos.getId())
                .setConnected1(true)
                .setDcNode2(dcNodeGbPos.getId())
                .setConnected2(true)
                .setR(5.0)
                .add();
        dcNetwork.getVoltageLevel(getVoltageLevelId(Country.FR, X_NODE_DC_1_FR, SUFFIX_150)).newLineCommutatedConverter()
                .setId("LccFr")
                .setBus1(getBusId(Country.FR, X_NODE_DC_1_FR, SUFFIX_150_1))
                .setBus2(getBusId(Country.FR, X_NODE_DC_1_FR, SUFFIX_150_2))
                .setDcNode1(dcNodeFrNeg.getId())
                .setDcNode2(dcNodeFrPos.getId())
                .setControlMode(AcDcConverter.ControlMode.V_DC)
                .setPccTerminal(dcNetwork.getLine(getLineId(Country.FR, X_NODE_DC_1_FR, SUFFIX_400_I)).getTerminal1())
                .setTargetVdc(500.)
                .setTargetP(200.)
                .add();
        dcNetwork.getVoltageLevel(getVoltageLevelId(Country.GB, X_NODE_DC_1_GB, SUFFIX_150)).newLineCommutatedConverter()
                .setId("LccGb")
                .setBus1(getBusId(Country.GB, X_NODE_DC_1_GB, SUFFIX_150_1))
                .setBus2(getBusId(Country.GB, X_NODE_DC_1_GB, SUFFIX_150_2))
                .setDcNode1(dcNodeGbNeg.getId())
                .setDcNode2(dcNodeGbPos.getId())
                .setControlMode(AcDcConverter.ControlMode.P_PCC)
                .setPccTerminal(dcNetwork.getThreeWindingsTransformer(getTransformerId(Country.GB, X_NODE_DC_1_GB, SUFFIX_NONE)).getLeg1().getTerminal())
                .setTargetVdc(500.)
                .setTargetP(-200.)
                .add();
        return Network.merge(dcNetwork, fr, gb);
    }

    public static Network createLccMonopoleGroundReturn() {
        return createLccMonopoleGroundReturn(NetworkFactory.findDefault());
    }

    public static Network createLccMonopoleGroundReturn(NetworkFactory networkFactory) {
        return createLccMonopoleBase(networkFactory, "LccMonopoleGroundReturn");
    }

    public static Network createLccMonopoleMetallicReturn() {
        return createLccMonopoleMetallicReturn(NetworkFactory.findDefault());
    }

    public static Network createLccMonopoleMetallicReturn(NetworkFactory networkFactory) {
        Network network = createLccMonopoleBase(networkFactory, "LccMonopoleMetallicReturn");
        network.getDcGround(DC_GROUND_GB).getDcTerminal().setConnected(false);
        network.getSubnetwork("LccMonopoleMetallicReturn")
                .newDcLine()
                .setId("dcLine2")
                .setDcNode1(DC_NODE_FR_NEG)
                .setConnected1(true)
                .setDcNode2(DC_NODE_GB_NEG)
                .setConnected2(true)
                .setR(5.0)
                .add();
        return network;
    }

    private static Network createVscMonopoleBase(NetworkFactory networkFactory, String dcNetworkId) {
        Objects.requireNonNull(networkFactory);
        Objects.requireNonNull(dcNetworkId);

        Network dcNetwork = networkFactory.createNetwork(dcNetworkId, "test");
        Network fr = createSimpleAcNetworkWithDanglingLines(networkFactory, Country.FR, Map.of(X_NODE_DC_1_FR, 200.));
        Network gb = createSimpleAcNetworkWithDanglingLines(networkFactory, Country.GB, Map.of(X_NODE_DC_1_GB, -200.));
        addDcAcElements(dcNetwork, Country.FR, X_NODE_DC_1_FR, -200., Mode.ONE_T2WT);
        addDcAcElements(dcNetwork, Country.GB, X_NODE_DC_1_GB, 200., Mode.ONE_T2WT);

        DcNode dcNodeFrPos = dcNetwork.newDcNode()
                .setId(DC_NODE_FR_POS)
                .setNominalV(250.)
                .add();
        DcNode dcNodeFrNeg = dcNetwork.newDcNode()
                .setId(DC_NODE_FR_NEG)
                .setNominalV(250.)
                .add();
        DcNode dcNodeGbPos = dcNetwork.newDcNode()
                .setId(DC_NODE_GB_POS)
                .setNominalV(250.)
                .add();
        DcNode dcNodeGbNeg = dcNetwork.newDcNode()
                .setId(DC_NODE_GB_NEG)
                .setNominalV(250.)
                .add();
        dcNetwork.newDcLine()
                .setId("dcLinePos")
                .setDcNode1(dcNodeFrPos.getId())
                .setDcNode2(dcNodeGbPos.getId())
                .setR(5.0)
                .add();
        dcNetwork.newDcLine()
                .setId("dcLineNeg")
                .setDcNode1(dcNodeFrNeg.getId())
                .setDcNode2(dcNodeGbNeg.getId())
                .setR(5.0)
                .add();
        Terminal frPccTerminal = dcNetwork.getTwoWindingsTransformer(getTransformerId(Country.FR, X_NODE_DC_1_FR, SUFFIX_NONE)).getTerminal1();
        dcNetwork.getVoltageLevel(getVoltageLevelId(Country.FR, X_NODE_DC_1_FR, SUFFIX_150)).newVoltageSourceConverter()
                .setId("VscFr")
                .setBus1(getBusId(Country.FR, X_NODE_DC_1_FR, SUFFIX_150))
                .setDcNode1(dcNodeFrNeg.getId())
                .setDcNode2(dcNodeFrPos.getId())
                .setControlMode(AcDcConverter.ControlMode.V_DC)
                .setPccTerminal(frPccTerminal)
                .setTargetVdc(500.)
                .setTargetP(200.)
                .setVoltageRegulatorOn(false)
                .setReactivePowerSetpoint(0.0)
                .setVoltageSetpoint(400.)
                .add();
        Terminal gbPccTerminal = dcNetwork.getTwoWindingsTransformer(getTransformerId(Country.GB, X_NODE_DC_1_GB, SUFFIX_NONE)).getTerminal1();
        dcNetwork.getVoltageLevel(getVoltageLevelId(Country.GB, X_NODE_DC_1_GB, SUFFIX_150)).newVoltageSourceConverter()
                .setId("VscGb")
                .setBus1(getBusId(Country.GB, X_NODE_DC_1_GB, SUFFIX_150))
                .setDcNode1(dcNodeGbNeg.getId())
                .setDcNode2(dcNodeGbPos.getId())
                .setControlMode(AcDcConverter.ControlMode.P_PCC)
                .setPccTerminal(gbPccTerminal)
                .setTargetVdc(500.)
                .setTargetP(-200.)
                .setVoltageRegulatorOn(false)
                .setReactivePowerSetpoint(0.0)
                .setVoltageSetpoint(400.)
                .add();
        return Network.merge(dcNetwork, fr, gb);
    }

    public static Network createVscSymmetricalMonopole() {
        return createVscSymmetricalMonopole(NetworkFactory.findDefault());
    }

    public static Network createVscSymmetricalMonopole(NetworkFactory networkFactory) {
        return createVscMonopoleBase(networkFactory, "VscSymmetricalMonopole");
    }

    public static Network createVscAsymmetricalMonopole() {
        return createVscAsymmetricalMonopole(NetworkFactory.findDefault());
    }

    public static Network createVscAsymmetricalMonopole(NetworkFactory networkFactory) {
        Network network = createVscMonopoleBase(networkFactory, "VscAsymmetricalMonopole");
        Network dcNetwork = network.getSubnetwork("VscAsymmetricalMonopole");
        dcNetwork.getDcLine("dcLineNeg").remove();
        dcNetwork.getDcNode(DC_NODE_FR_POS).setNominalV(500.);
        dcNetwork.getDcNode(DC_NODE_GB_POS).setNominalV(500.);
        dcNetwork.getDcNode(DC_NODE_FR_NEG).setNominalV(1.);
        dcNetwork.getDcNode(DC_NODE_GB_NEG).setNominalV(1.);
        dcNetwork.newDcGround()
                .setId(DC_GROUND_FR)
                .setDcNode(DC_NODE_FR_NEG)
                .setConnected(true)
                .setR(0.0)
                .add();
        dcNetwork.newDcGround()
                .setId(DC_GROUND_GB)
                .setDcNode(DC_NODE_GB_NEG)
                .setConnected(true)
                .setR(0.0)
                .add();
        return network;
    }
}