All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.powsybl.ucte.converter.UcteImporter Maven / Gradle / Ivy

There is a newer version: 6.6.0
Show newest version
/**
 * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium)
 * 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/.
 */
package com.powsybl.ucte.converter;

import com.google.auto.service.AutoService;
import com.google.common.base.Enums;
import com.google.common.base.Stopwatch;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import com.powsybl.commons.datasource.DataSource;
import com.powsybl.commons.datasource.ReadOnlyDataSource;
import com.powsybl.commons.reporter.Reporter;
import com.powsybl.entsoe.util.*;
import com.powsybl.iidm.import_.Importer;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.SlackTerminal;
import com.powsybl.ucte.network.*;
import com.powsybl.ucte.network.ext.UcteNetworkExt;
import com.powsybl.ucte.network.ext.UcteSubstation;
import com.powsybl.ucte.network.ext.UcteVoltageLevel;
import com.powsybl.ucte.network.io.UcteReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.powsybl.ucte.converter.util.UcteConstants.*;

/**
 * @author Geoffroy Jamgotchian 
 */
@AutoService(Importer.class)
public class UcteImporter implements Importer {

    private static final Logger LOGGER = LoggerFactory.getLogger(UcteImporter.class);

    private static final double LINE_MIN_Z = 0.05;

    private static final String[] EXTENSIONS = {"uct", "UCT"};

    private static double getConductance(UcteTransformer ucteTransfo) {
        double g = 0;
        if (!Double.isNaN(ucteTransfo.getConductance())) {
            g = ucteTransfo.getConductance();
        }
        return g;
    }

    private static double getSusceptance(UcteElement ucteElement) {
        double b = 0;
        if (!Double.isNaN(ucteElement.getSusceptance())) {
            b = ucteElement.getSusceptance();
        }
        return b;
    }

    private static boolean isFictitious(UcteElement ucteElement) {
        switch (ucteElement.getStatus()) {
            case EQUIVALENT_ELEMENT_IN_OPERATION:
            case EQUIVALENT_ELEMENT_OUT_OF_OPERATION:
                return true;
            case REAL_ELEMENT_IN_OPERATION:
            case REAL_ELEMENT_OUT_OF_OPERATION:
            case BUSBAR_COUPLER_IN_OPERATION:
            case BUSBAR_COUPLER_OUT_OF_OPERATION:
                return false;
            default:
                throw new AssertionError("Unexpected UcteElementStatus value: " + ucteElement.getStatus());
        }
    }

    private static boolean isFictitious(UcteNode ucteNode) {
        switch (ucteNode.getStatus()) {
            case EQUIVALENT:
                return true;
            case REAL:
                return false;
            default:
                throw new AssertionError("Unexpected UcteNodeStatus value: " + ucteNode.getStatus());
        }
    }

    /**
     * If the substation has a more specific geographical information than just its country,
     * returns the corresponding geographical code, otherwise null.
     */
    private static EntsoeGeographicalCode getRegionalGeographicalCode(Substation substation) {
        //Currently only DE has subregions
        if (substation.getCountry().map(country -> country != Country.DE).orElse(true)) {
            return null;
        }
        EntsoeGeographicalCode res = Enums.getIfPresent(EntsoeGeographicalCode.class, substation.getNameOrId().substring(0, 2)).orNull();
        //handle case where a D-node would start with DE ...
        return res == EntsoeGeographicalCode.DE ? null : res;
    }

    private static void createBuses(UcteNetworkExt ucteNetwork, UcteVoltageLevel ucteVoltageLevel, VoltageLevel voltageLevel) {
        for (UcteNodeCode ucteNodeCode : ucteVoltageLevel.getNodes()) {
            UcteNode ucteNode = ucteNetwork.getNode(ucteNodeCode);

            // skip Xnodes
            if (ucteNode.getCode().getUcteCountryCode() == UcteCountryCode.XX) {
                continue;
            }

            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Create bus '{}'", ucteNodeCode);
            }

            Bus bus = voltageLevel.getBusBreakerView().newBus()
                    .setId(ucteNodeCode.toString())
                    .setFictitious(isFictitious(ucteNode))
                    .add();

            addGeographicalNameProperty(ucteNode, bus);

            if (isValueValid(ucteNode.getActiveLoad()) || isValueValid(ucteNode.getReactiveLoad())) {
                createLoad(ucteNode, voltageLevel, bus);
            }

            if (ucteNode.isGenerator()) {
                createGenerator(ucteNode, voltageLevel, bus);
            }

            if (ucteNode.getTypeCode() == UcteNodeTypeCode.UT) {
                SlackTerminal.attach(bus);
            }
        }
    }

    private static void createBuses(UcteNetworkExt ucteNetwork, Network network) {
        for (UcteSubstation ucteSubstation : ucteNetwork.getSubstations()) {
            // skip substations with only one Xnode
            UcteNodeCode firstUcteNodeCode = ucteSubstation.getNodes().stream()
                    .filter(code -> code.getUcteCountryCode() != UcteCountryCode.XX)
                    .findFirst()
                    .orElse(null);
            if (firstUcteNodeCode == null) {
                continue;
            }

            LOGGER.trace("Create substation '{}'", ucteSubstation.getName());

            Substation substation = network.newSubstation()
                    .setId(ucteSubstation.getName())
                    .setCountry(EntsoeGeographicalCode.valueOf(firstUcteNodeCode.getUcteCountryCode().name()).getCountry())
                    .add();

            EntsoeGeographicalCode regionalCode = getRegionalGeographicalCode(substation);
            if (regionalCode != null) {
                substation.newExtension(EntsoeAreaAdder.class).withCode(regionalCode).add();
            }

            for (UcteVoltageLevel ucteVoltageLevel : ucteSubstation.getVoltageLevels()) {
                UcteVoltageLevelCode ucteVoltageLevelCode = ucteVoltageLevel.getNodes().iterator().next().getVoltageLevelCode();

                LOGGER.trace("Create voltage level '{}'", ucteVoltageLevel.getName());

                VoltageLevel voltageLevel = substation.newVoltageLevel()
                        .setId(ucteVoltageLevel.getName())
                        .setNominalV(ucteVoltageLevelCode.getVoltageLevel())
                        .setTopologyKind(TopologyKind.BUS_BREAKER)
                        .add();

                createBuses(ucteNetwork, ucteVoltageLevel, voltageLevel);
            }

        }
    }

    private static boolean isValueValid(double value) {
        return !Double.isNaN(value) && value != 0;
    }

    private static void createLoad(UcteNode ucteNode, VoltageLevel voltageLevel, Bus bus) {
        String loadId = bus.getId() + "_load";

        LOGGER.trace("Create load '{}'", loadId);

        double p0 = 0;
        if (isValueValid(ucteNode.getActiveLoad())) {
            p0 = ucteNode.getActiveLoad();
        }
        double q0 = 0;
        if (isValueValid(ucteNode.getReactiveLoad())) {
            q0 = ucteNode.getReactiveLoad();
        }

        voltageLevel.newLoad()
                .setId(loadId)
                .setBus(bus.getId())
                .setConnectableBus(bus.getId())
                .setP0(p0)
                .setQ0(q0)
                .add();
    }

    private static void createGenerator(UcteNode ucteNode, VoltageLevel voltageLevel, Bus bus) {
        String generatorId = bus.getId() + "_generator";

        LOGGER.trace("Create generator '{}'", generatorId);

        EnergySource energySource = EnergySource.OTHER;
        if (ucteNode.getPowerPlantType() != null) {
            switch (ucteNode.getPowerPlantType()) {
                case C:
                case G:
                case L:
                case O:
                    energySource = EnergySource.THERMAL;
                    break;
                case H:
                    energySource = EnergySource.HYDRO;
                    break;
                case N:
                    energySource = EnergySource.NUCLEAR;
                    break;
                case W:
                    energySource = EnergySource.WIND;
                    break;
                case F:
                    energySource = EnergySource.OTHER;
                    break;
                default:
                    throw new AssertionError("Unexpected UctePowerPlantType value: " + ucteNode.getPowerPlantType());
            }
        }

        double generatorP = isValueValid(ucteNode.getActivePowerGeneration()) ? -ucteNode.getActivePowerGeneration() : 0;
        double generatorQ = isValueValid(ucteNode.getReactivePowerGeneration()) ? -ucteNode.getReactivePowerGeneration() : 0;

        Generator generator = voltageLevel.newGenerator()
                .setId(generatorId)
                .setEnergySource(energySource)
                .setBus(bus.getId())
                .setConnectableBus(bus.getId())
                .setMinP(-ucteNode.getMinimumPermissibleActivePowerGeneration())
                .setMaxP(-ucteNode.getMaximumPermissibleActivePowerGeneration())
                .setVoltageRegulatorOn(ucteNode.isRegulatingVoltage())
                .setTargetP(generatorP)
                .setTargetQ(generatorQ)
                .setTargetV(ucteNode.getVoltageReference())
                .add();
        generator.newMinMaxReactiveLimits()
                .setMinQ(-ucteNode.getMinimumPermissibleReactivePowerGeneration())
                .setMaxQ(-ucteNode.getMaximumPermissibleReactivePowerGeneration())
                .add();
        if (ucteNode.getPowerPlantType() != null) {
            generator.setProperty(POWER_PLANT_TYPE_PROPERTY_KEY, ucteNode.getPowerPlantType().toString());
        }
    }

    private static void createDanglingLine(UcteLine ucteLine, boolean connected,
                                           UcteNode xnode, UcteNodeCode nodeCode, UcteVoltageLevel ucteVoltageLevel,
                                           Network network) {
        LOGGER.trace("Create dangling line '{}' (Xnode='{}')", ucteLine.getId(), xnode.getCode());

        double p0 = isValueValid(xnode.getActiveLoad()) ? xnode.getActiveLoad() : 0;
        double q0 = isValueValid(xnode.getReactiveLoad()) ? xnode.getReactiveLoad() : 0;
        double targetP = isValueValid(xnode.getActivePowerGeneration()) ? xnode.getActivePowerGeneration() : 0;
        double targetQ = isValueValid(xnode.getReactivePowerGeneration()) ? xnode.getReactivePowerGeneration() : 0;

        VoltageLevel voltageLevel = network.getVoltageLevel(ucteVoltageLevel.getName());
        DanglingLine dl = voltageLevel.newDanglingLine()
                .setId(ucteLine.getId().toString())
                .setName(xnode.getGeographicalName())
                .setBus(connected ? nodeCode.toString() : null)
                .setConnectableBus(nodeCode.toString())
                .setR(ucteLine.getResistance())
                .setX(ucteLine.getReactance())
                .setG(0)
                .setB(getSusceptance(ucteLine))
                .setP0(p0)
                .setQ0(q0)
                .setUcteXnodeCode(xnode.getCode().toString())
                .setFictitious(isFictitious(ucteLine))
                .newGeneration()
                .setTargetP(-targetP)
                .setTargetQ(-targetQ)
                .add()
                .add();

        if (xnode.isRegulatingVoltage()) {
            dl.getGeneration()
                    .setTargetV(xnode.getVoltageReference())
                    .setVoltageRegulationOn(true)
                    .setMaxP(-xnode.getMaximumPermissibleActivePowerGeneration())
                    .setMinP(-xnode.getMinimumPermissibleActivePowerGeneration());
            dl.getGeneration().newMinMaxReactiveLimits()
                    .setMinQ(-xnode.getMinimumPermissibleReactivePowerGeneration())
                    .setMaxQ(-xnode.getMaximumPermissibleReactivePowerGeneration())
                    .add();
        }

        dl.newExtension(XnodeAdder.class).withCode(xnode.getCode().toString()).add();

        if (ucteLine.getCurrentLimit() != null) {
            dl.newCurrentLimits()
                    .setPermanentLimit(ucteLine.getCurrentLimit())
                    .add();
        }

        addElementNameProperty(ucteLine, dl);
        addGeographicalNameProperty(xnode, dl);
        addXnodeStatusProperty(xnode, dl);
        addDanglingLineCouplerProperty(ucteLine, dl);
    }

    private static void createCoupler(UcteNetworkExt ucteNetwork, Network network,
                                      UcteLine ucteLine,
                                      UcteNodeCode nodeCode1, UcteNodeCode nodeCode2,
                                      UcteVoltageLevel ucteVoltageLevel1, UcteVoltageLevel ucteVoltageLevel2) {
        LOGGER.trace("Create coupler '{}'", ucteLine.getId());

        if (ucteVoltageLevel1 != ucteVoltageLevel2) {
            throw new UcteException("Coupler between two different voltage levels");
        }

        boolean connected = isConnected(ucteLine);

        if (nodeCode1.getUcteCountryCode() == UcteCountryCode.XX &&
                nodeCode2.getUcteCountryCode() != UcteCountryCode.XX) {
            // coupler connected to a XNODE (side 1)
            createDanglingLine(ucteLine, connected, ucteNetwork.getNode(nodeCode1), nodeCode2, ucteVoltageLevel2, network);
        } else if (nodeCode2.getUcteCountryCode() == UcteCountryCode.XX &&
                nodeCode1.getUcteCountryCode() != UcteCountryCode.XX) {
            // coupler connected to a XNODE (side 2)
            createDanglingLine(ucteLine, connected, ucteNetwork.getNode(nodeCode2), nodeCode1, ucteVoltageLevel1, network);
        } else {
            double z = Math.hypot(ucteLine.getResistance(), ucteLine.getReactance());
            createCouplerFromLowImpedanceLine(network, ucteLine, nodeCode1, nodeCode2, ucteVoltageLevel1, ucteVoltageLevel2, connected, z);
        }
    }

    private static void createCouplerFromLowImpedanceLine(Network network, UcteLine ucteLine,
                                                          UcteNodeCode nodeCode1, UcteNodeCode nodeCode2,
                                                          UcteVoltageLevel ucteVoltageLevel1, UcteVoltageLevel ucteVoltageLevel2,
                                                          boolean connected, double z) {
        LOGGER.info("Create coupler '{}' from low impedance line ({} ohm)", ucteLine.getId(), z);

        if (ucteVoltageLevel1 != ucteVoltageLevel2) {
            throw new UcteException("Nodes coupled with a low impedance line are expected to be in the same voltage level");
        }
        VoltageLevel voltageLevel = network.getVoltageLevel(ucteVoltageLevel1.getName());
        Switch couplerSwitch = voltageLevel.getBusBreakerView().newSwitch()
                .setEnsureIdUnicity(true)
                .setId(ucteLine.getId().toString())
                .setBus1(nodeCode1.toString())
                .setBus2(nodeCode2.toString())
                .setOpen(!connected)
                .setFictitious(isFictitious(ucteLine))
                .add();

        addCurrentLimitProperty(ucteLine, couplerSwitch);
        addOrderCodeProperty(ucteLine, couplerSwitch);
        addElementNameProperty(ucteLine, couplerSwitch);
    }

    private static void createStandardLine(Network network, UcteLine ucteLine, UcteNodeCode nodeCode1, UcteNodeCode nodeCode2,
                                           UcteVoltageLevel ucteVoltageLevel1, UcteVoltageLevel ucteVoltageLevel2,
                                           boolean connected) {
        LOGGER.trace("Create line '{}'", ucteLine.getId());

        Line l = network.newLine()
                .setEnsureIdUnicity(true)
                .setId(ucteLine.getId().toString())
                .setVoltageLevel1(ucteVoltageLevel1.getName())
                .setVoltageLevel2(ucteVoltageLevel2.getName())
                .setBus1(connected ? nodeCode1.toString() : null)
                .setBus2(connected ? nodeCode2.toString() : null)
                .setConnectableBus1(nodeCode1.toString())
                .setConnectableBus2(nodeCode2.toString())
                .setR(ucteLine.getResistance())
                .setX(ucteLine.getReactance())
                .setG1(0)
                .setG2(0)
                .setB1(getSusceptance(ucteLine) / 2)
                .setB2(getSusceptance(ucteLine) / 2)
                .setFictitious(isFictitious(ucteLine))
                .add();

        addElementNameProperty(ucteLine, l);

        if (ucteLine.getCurrentLimit() != null) {
            int currentLimit = ucteLine.getCurrentLimit();
            l.newCurrentLimits1()
                    .setPermanentLimit(currentLimit)
                    .add();
            l.newCurrentLimits2()
                    .setPermanentLimit(currentLimit)
                    .add();
        }
    }

    private static void createLine(UcteNetworkExt ucteNetwork, Network network,
                                   UcteLine ucteLine,
                                   UcteNodeCode nodeCode1, UcteNodeCode nodeCode2,
                                   UcteVoltageLevel ucteVoltageLevel1, UcteVoltageLevel ucteVoltageLevel2) {
        boolean connected = isConnected(ucteLine);

        double z = Math.hypot(ucteLine.getResistance(), ucteLine.getReactance());

        if (z < LINE_MIN_Z
                && nodeCode1.getUcteCountryCode() != UcteCountryCode.XX
                && nodeCode2.getUcteCountryCode() != UcteCountryCode.XX) {

            createCouplerFromLowImpedanceLine(network, ucteLine, nodeCode1, nodeCode2, ucteVoltageLevel1, ucteVoltageLevel2, connected, z);
        } else {

            if (nodeCode1.getUcteCountryCode() != UcteCountryCode.XX
                    && nodeCode2.getUcteCountryCode() != UcteCountryCode.XX) {

                createStandardLine(network, ucteLine, nodeCode1, nodeCode2, ucteVoltageLevel1, ucteVoltageLevel2, connected);

            } else if (nodeCode1.getUcteCountryCode() == UcteCountryCode.XX
                    && nodeCode2.getUcteCountryCode() != UcteCountryCode.XX) {

                UcteNode xnode = ucteNetwork.getNode(nodeCode1);

                createDanglingLine(ucteLine, connected, xnode, nodeCode2, ucteVoltageLevel2, network);

            } else if (nodeCode1.getUcteCountryCode() != UcteCountryCode.XX
                    && nodeCode2.getUcteCountryCode() == UcteCountryCode.XX) {

                UcteNode xnode = ucteNetwork.getNode(nodeCode2);

                createDanglingLine(ucteLine, connected, xnode, nodeCode1, ucteVoltageLevel1, network);

            } else {
                throw new UcteException("Line between 2 Xnodes");
            }
        }
    }

    private static void createLines(UcteNetworkExt ucteNetwork, Network network) {
        for (UcteLine ucteLine : ucteNetwork.getLines()) {
            UcteNodeCode nodeCode1 = ucteLine.getId().getNodeCode1();
            UcteNodeCode nodeCode2 = ucteLine.getId().getNodeCode2();
            UcteVoltageLevel ucteVoltageLevel1 = ucteNetwork.getVoltageLevel(nodeCode1);
            UcteVoltageLevel ucteVoltageLevel2 = ucteNetwork.getVoltageLevel(nodeCode2);

            switch (ucteLine.getStatus()) {
                case BUSBAR_COUPLER_IN_OPERATION:
                case BUSBAR_COUPLER_OUT_OF_OPERATION:
                    createCoupler(ucteNetwork, network, ucteLine, nodeCode1, nodeCode2, ucteVoltageLevel1, ucteVoltageLevel2);
                    break;

                case REAL_ELEMENT_IN_OPERATION:
                case REAL_ELEMENT_OUT_OF_OPERATION:
                case EQUIVALENT_ELEMENT_IN_OPERATION:
                case EQUIVALENT_ELEMENT_OUT_OF_OPERATION:
                    createLine(ucteNetwork, network, ucteLine, nodeCode1, nodeCode2, ucteVoltageLevel1, ucteVoltageLevel2);
                    break;

                default:
                    throw new AssertionError("Unexpected UcteElementStatus value: " + ucteLine.getStatus());
            }
        }
    }

    private static void createRatioTapChanger(UctePhaseRegulation uctePhaseRegulation, TwoWindingsTransformer transformer) {

        LOGGER.trace("Create ratio tap changer '{}'", transformer.getId());

        int lowerTap = getLowTapPosition(uctePhaseRegulation, transformer);
        RatioTapChangerAdder rtca = transformer.newRatioTapChanger()
                .setLowTapPosition(lowerTap)
                .setTapPosition(uctePhaseRegulation.getNp())
                .setLoadTapChangingCapabilities(!Double.isNaN(uctePhaseRegulation.getU()));
        if (!Double.isNaN(uctePhaseRegulation.getU())) {
            rtca.setLoadTapChangingCapabilities(true)
                    .setRegulating(true)
                    .setTargetV(uctePhaseRegulation.getU())
                    .setTargetDeadband(0.0)
                    .setRegulationTerminal(transformer.getTerminal1());
        }
        for (int i = lowerTap; i <= Math.abs(lowerTap); i++) {
            double rho = 1 / (1 + i * uctePhaseRegulation.getDu() / 100);
            rtca.beginStep()
                    .setRho(rho)
                    .setR(0)
                    .setX(0)
                    .setG(0)
                    .setB(0)
                    .endStep();
        }
        rtca.add();
    }

    private static void createPhaseTapChanger(UcteAngleRegulation ucteAngleRegulation, TwoWindingsTransformer transformer) {

        LOGGER.trace("Create phase tap changer '{}'", transformer.getId());
        int lowerTap = getLowTapPosition(ucteAngleRegulation, transformer);
        PhaseTapChangerAdder ptca = transformer.newPhaseTapChanger()
                .setLowTapPosition(lowerTap)
                .setTapPosition(ucteAngleRegulation.getNp())
                .setRegulationValue(ucteAngleRegulation.getP())
                .setRegulationMode(PhaseTapChanger.RegulationMode.FIXED_TAP);

        for (int i = lowerTap; i <= Math.abs(lowerTap); i++) {
            double rho;
            double alpha;
            double dx = i * ucteAngleRegulation.getDu() / 100 * Math.cos(Math.toRadians(ucteAngleRegulation.getTheta()));
            double dy = i * ucteAngleRegulation.getDu() / 100 * Math.sin(Math.toRadians(ucteAngleRegulation.getTheta()));
            switch (ucteAngleRegulation.getType()) {
                case ASYM:
                    rho = 1d / Math.hypot(dy, 1d + dx);
                    alpha = Math.toDegrees(Math.atan2(dy, 1 + dx));
                    break;

                case SYMM:
                    rho = 1d;
                    alpha = Math.toDegrees(2 * Math.atan2(dy, 2 * (1d + dx)));
                    break;

                default:
                    throw new AssertionError("Unexpected UcteAngleRegulationType value: " + ucteAngleRegulation.getType());
            }
            ptca.beginStep()
                    .setRho(rho)
                    .setAlpha(-alpha) // minus because in the UCT model PST is on side 2 and side1 on IIDM model
                    .setR(0)
                    .setX(0)
                    .setG(0)
                    .setB(0)
                    .endStep();
        }
        ptca.add();
    }

    private static int getLowTapPosition(UctePhaseRegulation uctePhaseRegulation, TwoWindingsTransformer transformer) {
        return getLowTapPosition(transformer, uctePhaseRegulation.getN(), uctePhaseRegulation.getNp());
    }

    private static int getLowTapPosition(UcteAngleRegulation ucteAngleRegulation, TwoWindingsTransformer transformer) {
        return getLowTapPosition(transformer, ucteAngleRegulation.getN(), ucteAngleRegulation.getNp());
    }

    private static int getLowTapPosition(TwoWindingsTransformer transformer, int initialTapsNumber, int currentTapPosition) {
        int floor;
        if (initialTapsNumber >= Math.abs(currentTapPosition)) {
            floor = -initialTapsNumber;
        } else {
            LOGGER.warn("Tap position for transformer '{}' is '{}', absolute value should be equal or lower than number of Taps '{}'", transformer.getId(), currentTapPosition, initialTapsNumber);
            if (currentTapPosition < 0) {
                floor = currentTapPosition;
            } else {
                floor  = -currentTapPosition;
            }
            LOGGER.info("Number of Taps for transformer '{}' is extended from '{}', to '{}'", transformer.getId(), initialTapsNumber, Math.abs(floor));
        }
        return floor;
    }

    private static TwoWindingsTransformer createXnodeTransfo(UcteNetworkExt ucteNetwork, UcteTransformer ucteTransfo, boolean connected,
                                                             UcteNodeCode xNodeCode, UcteNodeCode ucteOtherNodeCode, UcteVoltageLevel ucteOtherVoltageLevel,
                                                             Substation substation, EntsoeFileName ucteFileName) {
        // transfo connected to a XNODE, create an intermediate YNODE and small impedance line
        // otherNode--transfo--XNODE => otherNode--transfo--YNODE--line--XNODE
        String xNodeName = xNodeCode.toString();
        String yNodeName = ucteFileName.getCountry() != null ? ucteFileName.getCountry() + "_" + xNodeName : "YNODE_" + xNodeName;

        VoltageLevel yVoltageLevel = substation.newVoltageLevel()
                .setId(yNodeName + "_VL")
                .setNominalV(xNodeCode.getVoltageLevelCode().getVoltageLevel()) // nominal voltage of the XNODE
                .setTopologyKind(TopologyKind.BUS_BREAKER)
                .add();

        yVoltageLevel.getBusBreakerView().newBus()
                .setId(yNodeName)
                .setFictitious(true)
                .add();

        UcteNode ucteXnode = ucteNetwork.getNode(xNodeCode);

        LOGGER.warn("Create small impedance dangling line '{}{}' (transformer connected to XNODE '{}')",
                xNodeName, yNodeName, ucteXnode.getCode());

        double p0 = isValueValid(ucteXnode.getActiveLoad()) ? ucteXnode.getActiveLoad() : 0;
        double q0 = isValueValid(ucteXnode.getReactiveLoad()) ? ucteXnode.getReactiveLoad() : 0;
        double targetP = isValueValid(ucteXnode.getActivePowerGeneration()) ? ucteXnode.getActivePowerGeneration() : 0;
        double targetQ = isValueValid(ucteXnode.getReactivePowerGeneration()) ? ucteXnode.getReactivePowerGeneration() : 0;

        // create a small impedance dangling line connected to the YNODE
        DanglingLine yDanglingLine = yVoltageLevel.newDanglingLine()
                .setId(xNodeName + " " + yNodeName)
                .setBus(yNodeName)
                .setConnectableBus(yNodeName)
                .setR(0.0)
                .setX(LINE_MIN_Z)
                .setG(0)
                .setB(0)
                .setP0(p0)
                .setQ0(q0)
                .setUcteXnodeCode(ucteXnode.getCode().toString())
                .newGeneration()
                .setTargetP(-targetP)
                .setTargetQ(-targetQ)
                .add()
                .add();
        yDanglingLine.newExtension(XnodeAdder.class).withCode(ucteXnode.getCode().toString()).add();
        addXnodeStatusProperty(ucteXnode, yDanglingLine);
        addGeographicalNameProperty(ucteXnode, yDanglingLine);

        String voltageLevelId1;
        String voltageLevelId2;
        String busId1;
        String busId2;
        if (ucteXnode.getCode().equals(ucteTransfo.getId().getNodeCode1())) {
            voltageLevelId1 = ucteOtherVoltageLevel.getName();
            voltageLevelId2 = yVoltageLevel.getId();
            busId1 = ucteOtherNodeCode.toString();
            busId2 = yNodeName;
        } else {
            voltageLevelId1 = yVoltageLevel.getId();
            voltageLevelId2 = ucteOtherVoltageLevel.getName();
            busId1 = yNodeName;
            busId2 = ucteOtherNodeCode.toString();
        }

        // create a transformer connected to the YNODE and other node
        return substation.newTwoWindingsTransformer()
                .setEnsureIdUnicity(true)
                .setId(ucteTransfo.getId().toString())
                .setVoltageLevel1(voltageLevelId1)
                .setVoltageLevel2(voltageLevelId2)
                .setBus1(connected ? busId1 : null)
                .setBus2(connected ? busId2 : null)
                .setConnectableBus1(busId1)
                .setConnectableBus2(busId2)
                .setRatedU1(ucteTransfo.getRatedVoltage2())
                .setRatedU2(ucteTransfo.getRatedVoltage1())
                .setR(ucteTransfo.getResistance())
                .setX(ucteTransfo.getReactance())
                .setG(getConductance(ucteTransfo))
                .setB(getSusceptance(ucteTransfo))
                .add();

    }

    private static boolean isConnected(UcteElement ucteElement) {
        boolean connected;
        switch (ucteElement.getStatus()) {
            case REAL_ELEMENT_IN_OPERATION:
            case EQUIVALENT_ELEMENT_IN_OPERATION:
            case BUSBAR_COUPLER_IN_OPERATION:
                connected = true;
                break;

            case REAL_ELEMENT_OUT_OF_OPERATION:
            case EQUIVALENT_ELEMENT_OUT_OF_OPERATION:
            case BUSBAR_COUPLER_OUT_OF_OPERATION:
                connected = false;
                break;

            default:
                throw new AssertionError("Unexpected UcteElementStatus value: " + ucteElement.getStatus());
        }
        return connected;
    }

    private static void addTapChangers(UcteNetworkExt ucteNetwork, UcteTransformer ucteTransfo, TwoWindingsTransformer transformer) {
        UcteRegulation ucteRegulation = ucteNetwork.getRegulation(ucteTransfo.getId());
        if (ucteRegulation != null) {
            if (ucteRegulation.getPhaseRegulation() != null) {
                createRatioTapChanger(ucteRegulation.getPhaseRegulation(), transformer);
            }
            if (ucteRegulation.getAngleRegulation() != null) {
                createPhaseTapChanger(ucteRegulation.getAngleRegulation(), transformer);
            }
        }
    }

    private static void createTransformers(UcteNetworkExt ucteNetwork, Network network, EntsoeFileName ucteFileName) {
        for (UcteTransformer ucteTransfo : ucteNetwork.getTransformers()) {
            UcteNodeCode nodeCode1 = ucteTransfo.getId().getNodeCode1();
            UcteNodeCode nodeCode2 = ucteTransfo.getId().getNodeCode2();
            UcteVoltageLevel ucteVoltageLevel1 = ucteNetwork.getVoltageLevel(nodeCode1);
            UcteVoltageLevel ucteVoltageLevel2 = ucteNetwork.getVoltageLevel(nodeCode2);
            UcteSubstation ucteSubstation = ucteVoltageLevel1.getSubstation();
            Substation substation = network.getSubstation(ucteSubstation.getName());

            LOGGER.trace("Create transformer '{}'", ucteTransfo.getId());

            boolean connected = isConnected(ucteTransfo);

            TwoWindingsTransformer transformer;

            if (nodeCode1.getUcteCountryCode() == UcteCountryCode.XX &&
                    nodeCode2.getUcteCountryCode() != UcteCountryCode.XX) {
                // transformer connected to XNODE
                transformer = createXnodeTransfo(ucteNetwork, ucteTransfo, connected, nodeCode1, nodeCode2, ucteVoltageLevel2, substation, ucteFileName);

            } else if (nodeCode2.getUcteCountryCode() == UcteCountryCode.XX &&
                    nodeCode1.getUcteCountryCode() != UcteCountryCode.XX) {
                // transformer connected to XNODE
                transformer = createXnodeTransfo(ucteNetwork, ucteTransfo, connected, nodeCode2, nodeCode1, ucteVoltageLevel1, substation, ucteFileName);

            } else {
                // standard transformer
                transformer = substation.newTwoWindingsTransformer()
                        .setEnsureIdUnicity(true)
                        .setId(ucteTransfo.getId().toString())
                        .setVoltageLevel1(ucteVoltageLevel2.getName())
                        .setVoltageLevel2(ucteVoltageLevel1.getName())
                        .setBus1(connected ? nodeCode2.toString() : null)
                        .setBus2(connected ? nodeCode1.toString() : null)
                        .setConnectableBus1(nodeCode2.toString())
                        .setConnectableBus2(nodeCode1.toString())
                        .setRatedU1(ucteTransfo.getRatedVoltage2())
                        .setRatedU2(ucteTransfo.getRatedVoltage1())
                        .setR(ucteTransfo.getResistance())
                        .setX(ucteTransfo.getReactance())
                        .setG(getConductance(ucteTransfo))
                        .setB(getSusceptance(ucteTransfo))
                        .setFictitious(isFictitious(ucteTransfo))
                        .add();

            }

            if (ucteTransfo.getCurrentLimit() != null) {
                int currentLimit = ucteTransfo.getCurrentLimit();
                transformer.newCurrentLimits2()
                        .setPermanentLimit(currentLimit)
                        .add();
            }

            addElementNameProperty(ucteTransfo, transformer);
            addTapChangers(ucteNetwork, ucteTransfo, transformer);
            addNominalPowerProperty(ucteTransfo, transformer);
        }
    }

    private static String getBusId(Bus bus) {
        return bus != null ? bus.getId() : null;
    }

    private static DanglingLine getMatchingDanglingLine(DanglingLine dl1, Multimap danglingLinesByXnodeCode) {
        Xnode xnodExtension = dl1.getExtension(Xnode.class);
        if (xnodExtension == null) {
            throw new UcteException("Dangling line " + dl1.getNameOrId() + " doesn't have the Xnode extension");
        }
        String otherXnodeCode = xnodExtension.getCode();
        List matchingDanglingLines = danglingLinesByXnodeCode.get(otherXnodeCode)
                .stream().filter(dl -> dl != dl1)
                .collect(Collectors.toList());
        if (matchingDanglingLines.isEmpty()) {
            return null;
        } else if (matchingDanglingLines.size() == 1) {
            return matchingDanglingLines.get(0);
        } else {
            if (!dl1.getTerminal().isConnected()) {
                return null;
            }
            List connectedMatchingDanglingLines = matchingDanglingLines.stream()
                    .filter(dl -> dl.getTerminal().isConnected())
                    .collect(Collectors.toList());
            if (connectedMatchingDanglingLines.isEmpty()) {
                return null;
            }
            if (connectedMatchingDanglingLines.size() == 1) {
                return connectedMatchingDanglingLines.get(0);
            } else {
                throw new UcteException("More that 2 connected dangling lines have the same XNODE " + dl1.getUcteXnodeCode());
            }
        }
    }

    private static void addElementNameProperty(TieLine tieLine, DanglingLine dl1, DanglingLine dl2) {
        if (dl1.hasProperty(ELEMENT_NAME_PROPERTY_KEY)) {
            tieLine.setProperty(ELEMENT_NAME_PROPERTY_KEY + "_1", dl1.getProperty(ELEMENT_NAME_PROPERTY_KEY));
        }

        if (dl2.hasProperty(ELEMENT_NAME_PROPERTY_KEY)) {
            tieLine.setProperty(ELEMENT_NAME_PROPERTY_KEY + "_2", dl2.getProperty(ELEMENT_NAME_PROPERTY_KEY));
        }
    }

    private static void addElementNameProperty(UcteElement ucteElement, Identifiable identifiable) {
        if (ucteElement.getElementName() != null) {
            identifiable.setProperty(ELEMENT_NAME_PROPERTY_KEY, ucteElement.getElementName());
        }
    }

    private static void addCurrentLimitProperty(UcteLine ucteLine, Switch aSwitch) {
        if (ucteLine.getCurrentLimit() != null) {
            aSwitch.setProperty(CURRENT_LIMIT_PROPERTY_KEY, String.valueOf(ucteLine.getCurrentLimit()));
        }
    }

    private static void addGeographicalNameProperty(UcteNode ucteNode, Identifiable identifiable) {
        if (ucteNode.getGeographicalName() != null) {
            identifiable.setProperty(GEOGRAPHICAL_NAME_PROPERTY_KEY, ucteNode.getGeographicalName());
        }
    }

    private static void addGeographicalNameProperty(UcteNetwork ucteNetwork, TieLine tieLine, DanglingLine dl1, DanglingLine dl2) {
        Optional optUcteNodeCode = UcteNodeCode.parseUcteNodeCode(dl1.getUcteXnodeCode());

        if (optUcteNodeCode.isPresent()) {
            UcteNode ucteNode = ucteNetwork.getNode(optUcteNodeCode.get());
            tieLine.setProperty(GEOGRAPHICAL_NAME_PROPERTY_KEY, ucteNode.getGeographicalName());
        } else {
            throw new UcteException(NOT_POSSIBLE_TO_IMPORT);
        }
    }

    private static void addOrderCodeProperty(UcteLine ucteLine, Switch sw) {
        String ucteLineId = ucteLine.getId().toString();
        sw.setProperty(ORDER_CODE, String.valueOf(ucteLineId.charAt(ucteLineId.length() - 1)));
    }

    private static void addNominalPowerProperty(UcteTransformer transformer, TwoWindingsTransformer twoWindingsTransformer) {
        if (!Double.isNaN(transformer.getNominalPower())) {
            twoWindingsTransformer.setProperty(NOMINAL_POWER_KEY, String.valueOf(transformer.getNominalPower()));
        }
    }

    private static void addXnodeStatusProperty(UcteNode ucteNode, Identifiable identifiable) {
        identifiable.setProperty(STATUS_PROPERTY_KEY + "_XNode", ucteNode.getStatus().toString());
    }

    private static void addXnodeStatusProperty(TieLine tieLine, DanglingLine danglingLine) {
        tieLine.setProperty(STATUS_PROPERTY_KEY + "_XNode", danglingLine.getProperty(STATUS_PROPERTY_KEY + "_XNode"));
    }

    private static void addDanglingLineCouplerProperty(UcteLine ucteLine, DanglingLine danglingLine) {
        switch (ucteLine.getStatus()) {
            case BUSBAR_COUPLER_IN_OPERATION:
            case BUSBAR_COUPLER_OUT_OF_OPERATION:
                danglingLine.setProperty(IS_COUPLER_PROPERTY_KEY, "true");
                break;
            case REAL_ELEMENT_IN_OPERATION:
            case REAL_ELEMENT_OUT_OF_OPERATION:
            case EQUIVALENT_ELEMENT_IN_OPERATION:
            case EQUIVALENT_ELEMENT_OUT_OF_OPERATION:
                danglingLine.setProperty(IS_COUPLER_PROPERTY_KEY, "false");
                break;
        }
    }

    @Override
    public String getFormat() {
        return "UCTE";
    }

    @Override
    public String getComment() {
        return "UCTE-DEF";
    }

    private String findExtension(ReadOnlyDataSource dataSource, boolean throwException) throws IOException {
        for (String ext : EXTENSIONS) {
            if (dataSource.exists(null, ext)) {
                return ext;
            }
        }
        if (throwException) {
            throw new UcteException("File " + dataSource.getBaseName()
                    + "." + String.join("|", EXTENSIONS) + " not found");
        }
        return null;
    }

    @Override
    public boolean exists(ReadOnlyDataSource dataSource) {
        try {
            String ext = findExtension(dataSource, false);
            if (ext != null) {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(dataSource.newInputStream(null, ext)))) {
                    return new UcteReader().checkHeader(reader);
                }
            }
            return false;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    private void mergeXnodeDanglingLines(UcteNetwork ucteNetwork, Network network) {
        Multimap danglingLinesByXnodeCode = HashMultimap.create();
        for (DanglingLine dl : network.getDanglingLines()) {
            danglingLinesByXnodeCode.put(dl.getExtension(Xnode.class).getCode(), dl);
        }

        Set danglingLinesToProcess = Sets.newHashSet(network.getDanglingLines());
        while (!danglingLinesToProcess.isEmpty()) {
            DanglingLine dlToProcess = danglingLinesToProcess.iterator().next();
            DanglingLine dlMatchingDlToProcess = getMatchingDanglingLine(dlToProcess, danglingLinesByXnodeCode);

            if (dlMatchingDlToProcess != null) {
                // lexical sort to always end up with same merge line id
                boolean switchDanglingLinesOrder = dlToProcess.getId().compareTo(dlMatchingDlToProcess.getId()) > 0;
                DanglingLine dlAtSideOne = switchDanglingLinesOrder ? dlMatchingDlToProcess : dlToProcess;
                DanglingLine dlAtSideTwo = switchDanglingLinesOrder ? dlToProcess : dlMatchingDlToProcess;

                createTieLine(ucteNetwork, network, dlAtSideOne, dlAtSideTwo);

                dlToProcess.remove();
                dlMatchingDlToProcess.remove();

                danglingLinesToProcess.remove(dlMatchingDlToProcess);
            }
            danglingLinesToProcess.remove(dlToProcess);
        }
    }

    private void createTieLine(UcteNetwork ucteNetwork, Network network, DanglingLine dlAtSideOne, DanglingLine dlAtSideTwo) {
        // lexical sort to always end up with same merge line id
        String mergeLineId = dlAtSideOne.getId() + " + " + dlAtSideTwo.getId();

        // create XNODE merge extension
        // In case R1 and R2 (resp. X1 and X2) are zero, rdp (resp. xdp) is set to 0.5:
        // by default the line is split in the middle.
        // R1 = 0 and R2 = 0 (resp. X1 = 0 and X2 = 0) are recovered when splitting the mergedXnode anyway.
        double sumR = dlAtSideOne.getR() + dlAtSideTwo.getR();
        double sumX = dlAtSideOne.getX() + dlAtSideTwo.getX();
        double rdp = (sumR == 0.) ? 0.5 : dlAtSideOne.getR() / sumR;
        double xdp = (sumX == 0.) ? 0.5 : dlAtSideOne.getX() / sumX;
        String xnodeCode = dlAtSideOne.getExtension(Xnode.class).getCode();

        TieLine mergeLine = network.newTieLine()
                .setId(mergeLineId)
                .setVoltageLevel1(dlAtSideOne.getTerminal().getVoltageLevel().getId())
                .setConnectableBus1(getBusId(dlAtSideOne.getTerminal().getBusBreakerView().getConnectableBus()))
                .setBus1(getBusId(dlAtSideOne.getTerminal().getBusBreakerView().getBus()))
                .setVoltageLevel2(dlAtSideTwo.getTerminal().getVoltageLevel().getId())
                .setConnectableBus2(getBusId(dlAtSideTwo.getTerminal().getBusBreakerView().getConnectableBus()))
                .setBus2(getBusId(dlAtSideTwo.getTerminal().getBusBreakerView().getBus()))
                .newHalfLine1()
                .setId(dlAtSideOne.getId())
                .setR(dlAtSideOne.getR())
                .setX(dlAtSideOne.getX())
                .setB1(dlAtSideOne.getB())
                .setB2(0.0)
                .setG1(dlAtSideOne.getG())
                .setG2(0.0)
                .setFictitious(dlAtSideOne.isFictitious())
                .add()
                .newHalfLine2()
                .setId(dlAtSideTwo.getId())
                .setR(dlAtSideTwo.getR())
                .setX(dlAtSideTwo.getX())
                .setB1(0.0)
                .setB2(dlAtSideTwo.getB())
                .setG1(0.0)
                .setG2(dlAtSideTwo.getG())
                .setFictitious(dlAtSideTwo.isFictitious())
                .add()
                .setUcteXnodeCode(xnodeCode)
                .add();

        addElementNameProperty(mergeLine, dlAtSideOne, dlAtSideTwo);
        addGeographicalNameProperty(ucteNetwork, mergeLine, dlAtSideOne, dlAtSideTwo);
        addXnodeStatusProperty(mergeLine, dlAtSideOne);

        if (dlAtSideOne.getCurrentLimits() != null) {
            mergeLine.newCurrentLimits1()
                    .setPermanentLimit(dlAtSideOne.getCurrentLimits().getPermanentLimit()).add();
        }
        if (dlAtSideTwo.getCurrentLimits() != null) {
            mergeLine.newCurrentLimits2()
                    .setPermanentLimit(dlAtSideTwo.getCurrentLimits().getPermanentLimit()).add();
        }
        double b1dp = dlAtSideOne.getB() == 0 ? 0.5 : 1;
        double g1dp = dlAtSideOne.getG() == 0 ? 0.5 : 1;
        double b2dp = dlAtSideTwo.getB() == 0 ? 0.5 : 0;
        double g2dp = dlAtSideTwo.getG() == 0 ? 0.5 : 0;
        mergeLine.newExtension(MergedXnodeAdder.class)
                .withRdp(rdp).withXdp(xdp)
                .withLine1Name(dlAtSideOne.getId())
                .withLine1Fictitious(dlAtSideOne.isFictitious())
                .withB1dp(b1dp)
                .withG1dp(g1dp)
                .withLine2Name(dlAtSideTwo.getId())
                .withLine2Fictitious(dlAtSideTwo.isFictitious())
                .withB2dp(b2dp)
                .withG2dp(g2dp)
                .withCode(xnodeCode)
                .add();
    }

    @Override
    public void copy(ReadOnlyDataSource fromDataSource, DataSource toDataSource) {
        Objects.requireNonNull(fromDataSource);
        Objects.requireNonNull(toDataSource);
        try {
            String ext = findExtension(fromDataSource, true);
            try (InputStream is = fromDataSource.newInputStream(null, ext);
                 OutputStream os = toDataSource.newOutputStream(null, ext, false)) {
                ByteStreams.copy(is, os);
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    @Override
    public Network importData(ReadOnlyDataSource dataSource, NetworkFactory networkFactory, Properties parameters, Reporter reporter) {
        try {
            String ext = findExtension(dataSource, true);
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(dataSource.newInputStream(null, ext)))) {

                Stopwatch stopwatch = Stopwatch.createStarted();

                UcteNetworkExt ucteNetwork = new UcteNetworkExt(new UcteReader().read(reader, reporter), LINE_MIN_Z);
                String fileName = dataSource.getBaseName();

                EntsoeFileName ucteFileName = EntsoeFileName.parse(fileName);

                Network network = networkFactory.createNetwork(fileName, "UCTE");
                network.setCaseDate(ucteFileName.getDate());
                network.setForecastDistance(ucteFileName.getForecastDistance());

                createBuses(ucteNetwork, network);
                createLines(ucteNetwork, network);
                createTransformers(ucteNetwork, network, ucteFileName);

                mergeXnodeDanglingLines(ucteNetwork, network);

                stopwatch.stop();

                LOGGER.debug("UCTE import done in {} ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));

                return network;
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy