AbstractConverter.java

/**
 * Copyright (c) 2021, 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.psse.converter;

import java.util.*;
import java.util.stream.Stream;

import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.SlackTerminal;
import com.powsybl.iidm.network.util.Identifiables;
import com.powsybl.iidm.network.util.Networks;
import com.powsybl.psse.model.PsseException;
import com.powsybl.psse.model.pf.*;
import org.apache.commons.math3.complex.Complex;

import com.powsybl.iidm.network.util.ContainersMapping;

/**
 * @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
 * @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
 */
public abstract class AbstractConverter {

    private static final String FIXED_SHUNT_TAG = "-SH";
    private static final String SWITCHED_SHUNT_TAG = "-SwSH";
    private static final String TWO_TERMINAL_DC_TAG = "TwoTerminalDc-";
    private static final String VSC_DC_TRANSMISSION_LINE_TAG = "VscDcTransmissionLine-";
    private static final String FACTS_DEVICE_TAG = "FactsDevice-";
    private static final int MAX_BUS_LENGTH = 12;
    private static final int MAX_BRANCH_LENGTH = 40;
    private static final int MAX_PSSE_NODE_BY_SUBSTATION = 998;

    enum PsseEquipmentType {
        PSSE_LOAD("L"),
        PSSE_FIXED_SHUNT("F"),
        PSSE_GENERATOR("M"),
        PSSE_BRANCH("B"),
        PSSE_TWO_WINDING("2"),
        PSSE_THREE_WINDING("3"),
        PSSE_SWITCHED_SHUNT("S"),
        PSSE_INDUCTION_MACHINE("I"),
        PSSE_TWO_TERMINAL_DC_LINE("D"),
        PSSE_VSC_DC_LINE("V"),
        PSSE_MULTI_TERMINAL_LINE("N"),
        PSSE_FACTS_DEVICE("A");

        private final String textCode;

        PsseEquipmentType(String textCode) {
            this.textCode = textCode;
        }

        String getTextCode() {
            return textCode;
        }
    }

    AbstractConverter(Network network) {
        this.containersMapping = null;
        this.network = Objects.requireNonNull(network);
    }

    AbstractConverter(ContainersMapping containersMapping, Network network) {
        this.containersMapping = Objects.requireNonNull(containersMapping);
        this.network = Objects.requireNonNull(network);
    }

    ContainersMapping getContainersMapping() {
        return containersMapping;
    }

    Network getNetwork() {
        return network;
    }

    static String getSubstationIdFromPsseSubstationIds(Set<Integer> busNumbers) {
        return getSubstationId("Sub", busNumbers);
    }

    static String getSubstationIdFromBuses(Set<Integer> busNumbers) {
        return getSubstationId("S", busNumbers);
    }

    private static String getSubstationId(String tag, Set<Integer> numbers) {
        if (numbers.isEmpty()) {
            throw new PsseException("Unexpected empty numbers");
        }
        List<Integer> sortedNumbers = numbers.stream().sorted().toList();
        String substationId = tag + sortedNumbers.get(0);
        for (int i = 1; i < sortedNumbers.size(); i++) {
            substationId = substationId.concat(String.format("-%d", sortedNumbers.get(i)));
        }
        return substationId;
    }

    static String getVoltageLevelId(Set<Integer> busNumbers) {
        if (busNumbers.isEmpty()) {
            throw new PsseException("Unexpected empty busNumbers");
        }
        List<Integer> sortedBusNumbers = busNumbers.stream().sorted().toList();
        String voltageLevelId = "VL" + sortedBusNumbers.get(0);
        for (int i = 1; i < sortedBusNumbers.size(); i++) {
            voltageLevelId = voltageLevelId.concat(String.format("-%d", sortedBusNumbers.get(i)));
        }
        return voltageLevelId;
    }

    static List<Integer> extractBusesFromVoltageLevelId(String voltageLevelId) {
        List<Integer> buses = new ArrayList<>();
        if (voltageLevelId.length() <= 2 || !voltageLevelId.startsWith("VL")) {
            return buses;
        }
        List<String> busesText = Arrays.stream(voltageLevelId.substring(2).split("-")).toList();
        if (!busesText.stream().allMatch(busText -> busText.matches("[1-9]\\d*"))) {
            return buses;
        }
        busesText.forEach(busText -> buses.add(Integer.parseInt(busText)));
        return buses;
    }

    static String getBusId(int busNum) {
        return "B" + busNum;
    }

    static OptionalInt extractBusNumber(String configuredBusId) {
        if (configuredBusId.length() <= 1 || !configuredBusId.startsWith("B")) {
            return OptionalInt.empty();
        }
        String busNumber = configuredBusId.substring(1);
        return busNumber.matches("[1-9]\\d*") ? OptionalInt.of(Integer.parseInt(busNumber)) : OptionalInt.empty();
    }

    static String getFixedShuntId(int busI, String fixedShuntId) {
        return getBusId(busI) + FIXED_SHUNT_TAG + fixedShuntId;
    }

    static String getGeneratorId(int busI, String generatorId) {
        return getBusId(busI) + "-G" + generatorId;
    }

    static String getLineId(int busI, int busJ, String ckt) {
        return "L-" + busI + "-" + busJ + "-" + ckt;
    }

    static String getLoadId(int busI, String loadId) {
        return getBusId(busI) + "-L" + loadId;
    }

    static String getSwitchedShuntId(int busI, String id) {
        return getBusId(busI) + SWITCHED_SHUNT_TAG + id;
    }

    static String getTransformerId(int busI, int busJ, String ckt) {
        return "T-" + busI + "-" + busJ + "-" + ckt;
    }

    static String getTransformerId(int busI, int busJ, int busK, String ckt) {
        return "T-" + busI + "-" + busJ + "-" + busK + "-" + ckt;
    }

    // we can not use rectifierIp and inverterIp as it is managed with only one end in substationData
    // In Psse each two-terminal dc line must have a unique name (up to 12 characters)
    static String getTwoTerminalDcId(String name) {
        return TWO_TERMINAL_DC_TAG + name;
    }

    public static String extractTwoTerminalDcName(String twoTerminalDcId) {
        String name = twoTerminalDcId.replace(TWO_TERMINAL_DC_TAG, "");
        return name.substring(0, Math.min(12, name.length()));
    }

    static String getLccConverterId(Network network, PsseTwoTerminalDcTransmissionLine psseTwoTerminalDc, PsseTwoTerminalDcConverter converter) {
        return Identifiables.getUniqueId("LccConverter-" + converter.getIp() + "-" + psseTwoTerminalDc.getName(), id -> network.getLccConverterStation(id) != null);
    }

    static String getVscDcTransmissionLineId(String name) {
        return VSC_DC_TRANSMISSION_LINE_TAG + name;
    }

    public static String extractVscDcTransmissionLineName(String vscDcTransmissionLineId) {
        String name = vscDcTransmissionLineId.replace(VSC_DC_TRANSMISSION_LINE_TAG, "");
        return name.substring(0, Math.min(12, name.length()));
    }

    static String getVscConverterId(Network network, PsseVoltageSourceConverterDcTransmissionLine psseVscDcTransmissionLine, PsseVoltageSourceConverter converter) {
        return Identifiables.getUniqueId("VscConverter-" + converter.getIbus() + "-" + psseVscDcTransmissionLine.getName(), id -> network.getVscConverterStation(id) != null);
    }

    static String getSwitchId(String voltageLevelId, PsseSubstation.PsseSubstationSwitchingDevice switchingDevice) {
        return voltageLevelId + "-Sw-" + switchingDevice.getNi() + "-" + switchingDevice.getNj() + "-" + switchingDevice.getCkt();
    }

    static String busbarSectionId(String voltageLevelId, int node) {
        return String.format("%s-Busbar-%d", voltageLevelId, node);
    }

    static String getFactsDeviceId(String name) {
        return FACTS_DEVICE_TAG + name;
    }

    public static String extractFactsDeviceName(String factsDeviceId) {
        String name = factsDeviceId.replace(FACTS_DEVICE_TAG, "");
        return name.substring(0, Math.min(12, name.length()));
    }

    static String getNodeId(VoltageLevel voltageLevel, int node) {
        return voltageLevel.getId() + "-" + node;
    }

    static boolean isFixedShunt(ShuntCompensator shunt) {
        if (shunt.getId().contains(FIXED_SHUNT_TAG)) {
            return true;
        } else if (shunt.getId().contains(SWITCHED_SHUNT_TAG)) {
            return false;
        } else {
            return shunt.getMaximumSectionCount() == 1
                    && !shunt.isVoltageRegulatorOn()
                    && Double.isNaN(shunt.getTargetV());
        }
    }

    static boolean isTwoTerminalDcTransmissionLine(HvdcLine hvdcLine) {
        return hvdcLine.getConverterStation1().getHvdcType() == HvdcConverterStation.HvdcType.LCC;
    }

    static boolean isVscDcTransmissionLine(HvdcLine hvdcLine) {
        return !isTwoTerminalDcTransmissionLine(hvdcLine);
    }

    static List<String> getEquipmentListToBeExported(VoltageLevel voltageLevel) {
        List<String> equipmentListToBeExported = new ArrayList<>();
        for (Connectable<?> connectable : voltageLevel.getConnectables()) {
            if (isEquipmentToBeExported(connectable.getType())) {
                if (connectable.getType() == IdentifiableType.HVDC_CONVERTER_STATION) {
                    HvdcConverterStation<?> converterStation = (HvdcConverterStation<?>) connectable;
                    equipmentListToBeExported.add(converterStation.getHvdcLine().getId());
                } else if (connectable.getType() == IdentifiableType.DANGLING_LINE) {
                    DanglingLine danglingLine = (DanglingLine) connectable;
                    if (danglingLine.isPaired()) {
                        TieLine tieLine = danglingLine.getTieLine().orElseThrow();
                        equipmentListToBeExported.add(tieLine.getId());
                    } else {
                        equipmentListToBeExported.add(connectable.getId());
                    }
                } else {
                    equipmentListToBeExported.add(connectable.getId());
                }
            }
        }
        return equipmentListToBeExported.stream().sorted().toList();
    }

    private static boolean isEquipmentToBeExported(IdentifiableType type) {
        return switch (type) {
            case LOAD, GENERATOR, SHUNT_COMPENSATOR, LINE, TWO_WINDINGS_TRANSFORMER, THREE_WINDINGS_TRANSFORMER, HVDC_CONVERTER_STATION, STATIC_VAR_COMPENSATOR, DANGLING_LINE, BATTERY ->
                    true;
            case BUSBAR_SECTION, HVDC_LINE, SWITCH, TIE_LINE -> false;
            default -> throw new PsseException("Unexpected equipment type: " + type.name());
        };
    }

    static List<Terminal> getEquipmentTerminals(VoltageLevel voltageLevel, String equipmentId) {
        List<Terminal> terminals = new ArrayList<>();
        Connectable<?> connectable = voltageLevel.getNetwork().getConnectable(equipmentId);
        if (connectable != null) {
            terminals.addAll(connectable.getTerminals());
        } else {
            Identifiable<?> identifiable = voltageLevel.getNetwork().getIdentifiable(equipmentId);
            if (identifiable != null && identifiable.getType() == IdentifiableType.HVDC_LINE) {
                HvdcLine hvdcLine = (HvdcLine) identifiable;
                terminals.add(hvdcLine.getConverterStation1().getTerminal());
                terminals.add(hvdcLine.getConverterStation2().getTerminal());
            } else if (identifiable != null && identifiable.getType() == IdentifiableType.TIE_LINE) {
                TieLine tieLine = (TieLine) identifiable;
                terminals.add(tieLine.getDanglingLine1().getTerminal());
                terminals.add(tieLine.getDanglingLine2().getTerminal());
            } else {
                throw new PsseException("Unexpected identifiable: " + equipmentId);
            }
        }
        return terminals;
    }

    static String getNodeBreakerEquipmentId(PsseEquipmentType equipmentType, int busI, String id) {
        return getNodeBreakerEquipmentId(equipmentType, busI, 0, 0, id);
    }

    static String getNodeBreakerEquipmentId(PsseEquipmentType equipmentType, int busI, int busJ, String id) {
        return getNodeBreakerEquipmentId(equipmentType, busI, busJ, 0, id);
    }

    static String getNodeBreakerEquipmentId(PsseEquipmentType equipmentType, int busI, int busJ, int busK, String id) {
        return getNodeBreakerEquipmentId(equipmentType.getTextCode(), busI, busJ, busK, id);
    }

    // EquipmentId must be independent of the bus order
    static String getNodeBreakerEquipmentId(String type, int busI, int busJ, int busK, String id) {
        List<Integer> sortedBuses = Stream.of(busI, busJ, busK).sorted().toList();
        int bus1 = sortedBuses.get(0);
        int bus2 = sortedBuses.get(1);
        int bus3 = sortedBuses.get(2);

        return type + "." + bus1 + "." + bus2 + "." + bus3 + "." + id;
    }

    static String getPsseEquipmentType(Identifiable<?> identifiable) {
        return switch (identifiable.getType()) {
            case LOAD, BATTERY -> PsseEquipmentType.PSSE_LOAD.getTextCode();
            case GENERATOR -> PsseEquipmentType.PSSE_GENERATOR.getTextCode();
            case LINE, TIE_LINE, DANGLING_LINE -> PsseEquipmentType.PSSE_BRANCH.getTextCode();
            case TWO_WINDINGS_TRANSFORMER -> PsseEquipmentType.PSSE_TWO_WINDING.getTextCode();
            case THREE_WINDINGS_TRANSFORMER -> PsseEquipmentType.PSSE_THREE_WINDING.getTextCode();
            case SHUNT_COMPENSATOR -> {
                ShuntCompensator shunt = (ShuntCompensator) identifiable;
                yield isFixedShunt(shunt) ? PsseEquipmentType.PSSE_FIXED_SHUNT.getTextCode() : PsseEquipmentType.PSSE_SWITCHED_SHUNT.getTextCode();
            }
            case HVDC_LINE -> {
                HvdcLine hvdcLine = (HvdcLine) identifiable;
                yield isTwoTerminalDcTransmissionLine(hvdcLine) ? PsseEquipmentType.PSSE_TWO_TERMINAL_DC_LINE.getTextCode() : PsseEquipmentType.PSSE_VSC_DC_LINE.getTextCode();
            }
            case STATIC_VAR_COMPENSATOR -> PsseEquipmentType.PSSE_FACTS_DEVICE.getTextCode();
            default -> throw new PsseException("unexpected identifiableType: " + identifiable.getType().name());
        };
    }

    // busI, busJ, busK and busEnd attributes are required to support lines and transformers within a voltageLevel
    static String getNodeBreakerEquipmentIdBus(String equipmentId, int bus, int end) {
        return equipmentId + "." + bus + "." + end;
    }

    static String getNodeBreakerEquipmentIdBus(String equipmentId, int busI, int busJ, int busK, int bus, String busEnd) {
        return equipmentId + "." + bus + "." + getBusEnd(busI, busJ, busK, bus, busEnd);
    }

    private static int getBusEnd(int busI, int busJ, int busK, int bus, String busEnd) {
        if (busI > 0 && busJ == 0 && busK == 0 && busI == bus) {
            return 1;
        }
        List<String> buses = new ArrayList<>();
        if (busI > 0 && busJ > 0 && busK == 0) {
            buses.add(String.format("%07d.I", busI));
            buses.add(String.format("%07d.J", busJ));
        }
        if (busI > 0 && busJ > 0 && busK > 0) {
            buses.add(String.format("%07d.I", busI));
            buses.add(String.format("%07d.J", busJ));
            buses.add(String.format("%07d.K", busK));
        }
        List<String> sortedBuses = buses.stream().sorted().toList();
        int index = sortedBuses.indexOf(String.format("%07d.%s", bus, busEnd));
        if (index == -1) {
            throw new PsseException("Unexpected attributes busI: " + busI + " busJ: " + busJ + " busK: " + busK + " bus: " + bus + " busEnd: " + busEnd);
        }
        return index + 1;
    }

    static Terminal findTerminalNode(Network network, String voltageLevelId, int node) {
        VoltageLevel voltageLevel = network.getVoltageLevel(voltageLevelId);
        return voltageLevel != null ? findTerminalNode(voltageLevel, node) : null;
    }

    static Terminal findTerminalNode(VoltageLevel voltageLevel, int node) {
        return voltageLevel.getNodeBreakerView().getOptionalTerminal(node)
                .orElseGet(() -> Networks.getEquivalentTerminal(voltageLevel, node));
    }

    static Optional<Bus> findConnectedBusViewNode(VoltageLevel voltageLevel, int node) {
        return Optional.ofNullable(findTerminalNode(voltageLevel, node))
                .map(Terminal::getBusView)
                .map(Terminal.BusView::getBus);
    }

    static Bus getTerminalConnectableBusView(Terminal terminal) {
        return terminal.getBusView().getBus() != null ? terminal.getBusView().getBus() : terminal.getBusView().getConnectableBus();
    }

    static int getTerminalBusI(Terminal terminal, ContextExport contextExport) {
        if (contextExport.getFullExport().isExportedAsNodeBreaker(terminal.getVoltageLevel())) {
            return contextExport.getFullExport().getBusI(terminal.getVoltageLevel(), terminal.getNodeBreakerView().getNode()).orElseThrow();
        } else {
            Bus bus = getTerminalConnectableBusView(terminal);
            return contextExport.getFullExport().getBusI(bus).orElseThrow();
        }
    }

    static int getRegulatingTerminalNode(Terminal regulatingTerminal, ContextExport contextExport) {
        if (regulatingTerminal == null) {
            return 0;
        } else {
            if (contextExport.getFullExport().isExportedAsNodeBreaker(regulatingTerminal.getVoltageLevel())) {
                return contextExport.getFullExport().getPsseNode(regulatingTerminal.getVoltageLevel(), regulatingTerminal.getNodeBreakerView().getNode()).orElseThrow();
            } else {
                return 0;
            }
        }
    }

    // zero can be used for local regulation
    static int getRegulatingTerminalBusI(Terminal regulatingTerminal, int busI, int previousRegulatingBusI, ContextExport contextExport) {
        int regulatingBusI = getRegulatingTerminalBusI(regulatingTerminal, contextExport);
        return busI == regulatingBusI && previousRegulatingBusI == 0 ? previousRegulatingBusI : regulatingBusI;
    }

    static int getRegulatingTerminalBusI(Terminal regulatingTerminal, ContextExport contextExport) {
        if (regulatingTerminal == null) {
            return 0;
        } else {
            return getTerminalBusI(regulatingTerminal, contextExport);
        }
    }

    static int getStatus(Terminal terminal, ContextExport contextExport) {
        if (contextExport.getFullExport().isExportedAsNodeBreaker(terminal.getVoltageLevel())) {
            return contextExport.getFullExport().isDeEnergized(terminal.getVoltageLevel(), terminal.getNodeBreakerView().getNode()) ? 0 : 1;
        } else {
            return getUpdatedStatus(terminal);
        }
    }

    static int getStatus(Terminal terminal1, Terminal terminal2, ContextExport contextExport) {
        return getStatus(terminal1, contextExport) == 1 && getStatus(terminal2, contextExport) == 1 ? 1 : 0;
    }

    static int getUpdatedStatus(Terminal terminal) {
        return terminal.isConnected() && terminal.getBusView().getBus() != null ? 1 : 0;
    }

    static int getUpdatedStatus(Terminal terminal1, Terminal terminal2) {
        return getUpdatedStatus(terminal1) == 1 && getUpdatedStatus(terminal2) == 1 ? 1 : 0;
    }

    static int findBusViewBusType(Bus bus) {
        if (bus == null || !bus.isInMainConnectedComponent()) {
            return 4;
        }
        SlackTerminal slackTerminal = bus.getVoltageLevel().getExtension(SlackTerminal.class);
        if (slackTerminal != null
                && slackTerminal.getTerminal().getBusView().getBus() != null
                && bus.getId().equals(slackTerminal.getTerminal().getBusView().getBus().getId())) {
            return 3;
        }
        return bus.getGeneratorStream().anyMatch(AbstractConverter::withLocalRegulatingControl) ? 2 : 1;
    }

    private static boolean withLocalRegulatingControl(Generator generator) {
        Bus generatorBus = generator.getTerminal().getBusView().getBus();
        Bus regulatedBus = generator.getRegulatingTerminal().getBusView().getBus();
        return generator.isVoltageRegulatorOn() && generatorBus != null && regulatedBus != null && generatorBus.getId().equals(regulatedBus.getId());
    }

    // node numbers in psse must be between 1 and 999
    // node psse 999 is used for mapping the node 0 of iidm
    static boolean exportVoltageLevelAsNodeBreaker(VoltageLevel voltageLevel) {
        return voltageLevel.getTopologyKind() == TopologyKind.NODE_BREAKER
                && voltageLevel.getNodeBreakerView().getSwitchCount() > 0
                && maxNode(voltageLevel) <= getMaxPsseNodeBySubstation();
    }

    static int getMaxPsseNodeBySubstation() {
        return MAX_PSSE_NODE_BY_SUBSTATION;
    }

    private static int maxNode(VoltageLevel voltageLevel) {
        return Arrays.stream(voltageLevel.getNodeBreakerView().getNodes()).max().orElse(0);
    }

    static Complex impedanceToEngineeringUnits(Complex impedance, double vnom, double sbase) {
        return impedance.multiply(vnom * vnom / sbase);
    }

    static double impedanceToEngineeringUnits(double impedance, double vnom, double sbase) {
        return impedance * vnom * vnom / sbase;
    }

    static double impedanceToEngineeringUnitsForLinesWithDifferentNominalVoltageAtEnds(double impedance, double vnom1, double vnom2, double sbase) {
        return impedance * vnom1 * vnom2 / sbase;
    }

    static Complex admittanceToEngineeringUnits(Complex admittance, double vnom, double sbase) {
        return admittance.multiply(sbase / (vnom * vnom));
    }

    static double admittanceEnd1ToEngineeringUnitsForLinesWithDifferentNominalVoltageAtEnds(double admittanceTransmissionEu, double shuntAdmittance, double vnom1, double vnom2, double sbase) {
        return shuntAdmittance * sbase / (vnom1 * vnom1) - (1 - vnom2 / vnom1) * admittanceTransmissionEu;
    }

    static double admittanceEnd2ToEngineeringUnitsForLinesWithDifferentNominalVoltageAtEnds(double admittanceTransmissionEu, double shuntAdmittance, double vnom1, double vnom2, double sbase) {
        return shuntAdmittance * sbase / (vnom2 * vnom2) - (1 - vnom1 / vnom2) * admittanceTransmissionEu;
    }

    static double admittanceToEngineeringUnits(double admittance, double vnom, double sbase) {
        return admittance * sbase / (vnom * vnom);
    }

    static double impedanceToPerUnitForLinesWithDifferentNominalVoltageAtEnds(double impedance, double vnom1, double vnom2, double sbase) {
        return impedance * sbase / (vnom1 * vnom2);
    }

    static double admittanceEnd1ToPerUnitForLinesWithDifferentNominalVoltageAtEnds(double admittanceTransmission, double shuntAdmittance, double vnom1, double vnom2, double sbase) {
        return (shuntAdmittance + (1 - vnom2 / vnom1) * admittanceTransmission) * vnom1 * vnom1 / sbase;
    }

    static double admittanceEnd2ToPerUnitForLinesWithDifferentNominalVoltageAtEnds(double admittanceTransmission, double shuntAdmittance, double vnom1, double vnom2, double sbase) {
        return (shuntAdmittance + (1 - vnom1 / vnom2) * admittanceTransmission) * vnom2 * vnom2 / sbase;
    }

    static Complex impedanceToPerUnit(Complex impedance, double vnom, double sbase) {
        return impedance.multiply(sbase / (vnom * vnom));
    }

    static Complex admittanceToPerUnit(Complex admittance, double vnom, double sbase) {
        return admittance.multiply(vnom * vnom / sbase);
    }

    static double powerToShuntAdmittance(double power, double vnom) {
        return power / (vnom * vnom);
    }

    static double shuntAdmittanceToPower(double shuntAdmittance, double vnom) {
        return shuntAdmittance * vnom * vnom;
    }

    static double getHighVm(Bus bus) {
        return bus != null && Double.isFinite(bus.getVoltageLevel().getHighVoltageLimit()) && bus.getVoltageLevel().getHighVoltageLimit() > 0.0 ? bus.getVoltageLevel().getHighVoltageLimit() / bus.getVoltageLevel().getNominalV() : 1.1;
    }

    static double getLowVm(Bus bus) {
        return bus != null && Double.isFinite(bus.getVoltageLevel().getLowVoltageLimit()) && bus.getVoltageLevel().getLowVoltageLimit() > 0.0 ? bus.getVoltageLevel().getLowVoltageLimit() / bus.getVoltageLevel().getNominalV() : 0.9;
    }

    static double getVm(Bus bus) {
        return bus != null ? getVm(bus.getV() / bus.getVoltageLevel().getNominalV()) : 1.0;
    }

    static double getVm(double v) {
        return Double.isFinite(v) && v > 0.0 ? v : 1.0;
    }

    static double getVa(Bus bus) {
        return bus != null ? getVa(bus.getAngle()) : 0.0;
    }

    static double getVa(double a) {
        return Double.isFinite(a) ? a : 0.0;
    }

    static List<Double> getSortedRates(CurrentLimits currentLimits, double nominalV) {
        List<Double> rates = new ArrayList<>();
        rates.add(convertToMva(currentLimits.getPermanentLimit(), nominalV));
        rates.addAll(currentLimits.getTemporaryLimits().stream().map(temporaryLimit -> convertToMva(temporaryLimit.getValue(), nominalV)).toList());
        return rates.stream().sorted().toList();
    }

    static List<Double> getSortedRates(ApparentPowerLimits apparentPowerLimits) {
        List<Double> rates = new ArrayList<>();
        rates.add(apparentPowerLimits.getPermanentLimit());
        rates.addAll(apparentPowerLimits.getTemporaryLimits().stream().map(LoadingLimits.TemporaryLimit::getValue).toList());
        return rates.stream().sorted().toList();
    }

    static List<Double> getSortedRates(ActivePowerLimits activePowerLimits) {
        List<Double> rates = new ArrayList<>();
        rates.add(activePowerLimits.getPermanentLimit());
        rates.addAll(activePowerLimits.getTemporaryLimits().stream().map(LoadingLimits.TemporaryLimit::getValue).toList());
        return rates.stream().sorted().toList();
    }

    private static double convertToMva(double current, double nominalV) {
        return (current / 1000.0) * Math.sqrt(3.0) * nominalV;
    }

    static void setSortedRatesToPsseRates(List<Double> sortedRates, PsseRates rates) {
        rates.setRate1(getRate(sortedRates, 0));
        rates.setRate2(getRate(sortedRates, 1));
        rates.setRate3(getRate(sortedRates, 2));
        rates.setRate4(getRate(sortedRates, 3));
        rates.setRate5(getRate(sortedRates, 4));
        rates.setRate6(getRate(sortedRates, 5));
        rates.setRate7(getRate(sortedRates, 6));
        rates.setRate8(getRate(sortedRates, 7));
        rates.setRate9(getRate(sortedRates, 8));
        rates.setRate10(getRate(sortedRates, 9));
        rates.setRate11(getRate(sortedRates, 10));
        rates.setRate12(getRate(sortedRates, 11));
    }

    private static double getRate(List<Double> sortedRates, int index) {
        return sortedRates.size() > index ? sortedRates.get(index) : 0.0;
    }

    static PsseBus createDefaultBus() {
        PsseBus psseBus = new PsseBus();
        psseBus.setI(0);
        psseBus.setName("");
        psseBus.setBaskv(0.0);
        psseBus.setIde(1);
        psseBus.setArea(1);
        psseBus.setZone(1);
        psseBus.setOwner(1);
        psseBus.setVm(1.0);
        psseBus.setVa(0.0);
        psseBus.setNvhi(1.1);
        psseBus.setNvlo(0.9);
        psseBus.setEvhi(1.1);
        psseBus.setEvlo(0.9);
        return psseBus;
    }

    // first character must not be a minus sign
    static String fixBusName(String name) {
        String fixedName = name.startsWith("-") ? "_" + name.substring(1) : name;
        return fixedName.length() > MAX_BUS_LENGTH ? fixedName.substring(0, MAX_BUS_LENGTH) : fixedName;
    }

    static PsseLoad createDefaultLoad() {
        PsseLoad psseLoad = new PsseLoad();
        psseLoad.setI(0);
        psseLoad.setId("1");
        psseLoad.setStatus(1);
        psseLoad.setArea(1);
        psseLoad.setZone(1);
        psseLoad.setPl(0.0);
        psseLoad.setQl(0.0);
        psseLoad.setIp(0.0);
        psseLoad.setIq(0.0);
        psseLoad.setYp(0.0);
        psseLoad.setYq(0.0);
        psseLoad.setOwner(1);
        psseLoad.setScale(1);
        psseLoad.setIntrpt(0);
        psseLoad.setDgenp(0.0);
        psseLoad.setDgenq(0.0);
        psseLoad.setDgenm(0);
        psseLoad.setLoadtype("");
        return psseLoad;
    }

    static PsseNonTransformerBranch createDefaultNonTransformerBranch() {
        PsseNonTransformerBranch psseLine = new PsseNonTransformerBranch();
        psseLine.setI(0);
        psseLine.setJ(0);
        psseLine.setCkt("1");
        psseLine.setR(0.0);
        psseLine.setX(0.0);
        psseLine.setB(0.0);
        psseLine.setName("");
        psseLine.setRates(createDefaultRates());
        psseLine.setGi(0.0);
        psseLine.setBi(0.0);
        psseLine.setGj(0.0);
        psseLine.setBj(0.0);
        psseLine.setSt(1);
        psseLine.setMet(1);
        psseLine.setLen(0.0);
        psseLine.setOwnership(createDefaultOwnership());
        return psseLine;
    }

    static String fixNonTransformerBranchName(String name) {
        return name.substring(0, Math.min(MAX_BRANCH_LENGTH, name.length()));
    }

    static PsseGenerator createDefaultGenerator() {
        PsseGenerator psseGenerator = new PsseGenerator();
        psseGenerator.setI(0);
        psseGenerator.setId("1");
        psseGenerator.setPg(0.0);
        psseGenerator.setQg(0.0);
        psseGenerator.setQt(9999.0);
        psseGenerator.setQb(-9999.0);
        psseGenerator.setVs(1.0);
        psseGenerator.setIreg(0);
        psseGenerator.setNreg(0);
        psseGenerator.setMbase(100.0);
        psseGenerator.setZr(0.0);
        psseGenerator.setZx(1.0);
        psseGenerator.setRt(0.0);
        psseGenerator.setXt(0.0);
        psseGenerator.setGtap(1.0);
        psseGenerator.setStat(1);
        psseGenerator.setRmpct(100.0);
        psseGenerator.setPt(9999.0);
        psseGenerator.setPb(-9999.0);
        psseGenerator.setBaslod(0);
        psseGenerator.setOwnership(createDefaultOwnership());
        psseGenerator.setWmod(0);
        psseGenerator.setWpf(1.0);
        return psseGenerator;
    }

    static PsseRates createDefaultRates() {
        PsseRates windingRates = new PsseRates();
        windingRates.setRate1(0.0);
        windingRates.setRate2(0.0);
        windingRates.setRate3(0.0);
        windingRates.setRate4(0.0);
        windingRates.setRate5(0.0);
        windingRates.setRate6(0.0);
        windingRates.setRate7(0.0);
        windingRates.setRate8(0.0);
        windingRates.setRate9(0.0);
        windingRates.setRate10(0.0);
        windingRates.setRate11(0.0);
        windingRates.setRate12(0.0);
        return windingRates;
    }

    static PsseOwnership createDefaultOwnership() {
        PsseOwnership psseOwnership = new PsseOwnership();
        psseOwnership.setO1(1);
        psseOwnership.setF1(1.0);
        psseOwnership.setO2(0);
        psseOwnership.setF2(1.0);
        psseOwnership.setO3(0);
        psseOwnership.setF3(1.0);
        psseOwnership.setO4(0);
        psseOwnership.setF4(1.0);
        return psseOwnership;
    }

    static double currentInAmpsToMw(double current, double nominalV) {
        return current * nominalV / 1000.0;
    }

    private final ContainersMapping containersMapping;
    private final Network network;
}