PsseValidation.java

/**
 * Copyright (c) 2020, 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.model.pf;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.powsybl.psse.model.PsseVersion;
import static com.powsybl.psse.model.PsseVersion.Major.V35;

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

    private final List<String> validationWarnings;
    private final List<String> validationErrors;
    private boolean validCase;
    private static final Logger LOGGER = LoggerFactory.getLogger(PsseValidation.class);

    private static final String ERROR_TRANSFORMER_1_PARAMETER = "Transformer: %s Unexpected %s: %.5f";
    private static final String ERROR_TRANSFORMER_2_PARAMETERS = "Transformer: %s Unexpected %s: %.5f %.5f";

    public PsseValidation(PssePowerFlowModel model, PsseVersion psseVersion) {
        Objects.requireNonNull(model);
        validationWarnings = new ArrayList<>();
        validationErrors = new ArrayList<>();
        validCase = true;

        validate(model, psseVersion);
        writeValidationWarnings();

        if (!validCase) {
            writeValidationErrors();
        }
    }

    public List<String> getValidationErrors() {
        return validationErrors;
    }

    public boolean isValidCase() {
        return validCase;
    }

    private void writeValidationWarnings() {
        LOGGER.warn("PSS/E Validation warnings ...");
        validationWarnings.forEach(LOGGER::warn);
        LOGGER.warn("PSS/E Validation warnings end.");
    }

    private void writeValidationErrors() {
        LOGGER.warn("PSS/E Validation errors ...");
        validationErrors.forEach(LOGGER::warn);
        LOGGER.warn("PSS/E Validation errors end. ValidCase {}", validCase);
    }

    private void validate(PssePowerFlowModel model, PsseVersion psseVersion) {

        Map<Integer, List<Integer>> buses = generateBuses(model.getBuses());

        validateCaseIdentification(model.getCaseIdentification());
        validateBuses(model.getBuses(), buses);
        validateLoads(model.getLoads(), buses);
        validateFixedShunts(model.getFixedShunts(), buses);
        validateGenerators(model.getBuses(), model.getGenerators(), buses);
        validateNonTransformerBranches(model.getNonTransformerBranches(), buses);
        validateTransformers(model.getTransformers(), buses);
        validateTwoTerminalDcTransmissionLines(model.getTwoTerminalDcTransmissionLines(), buses);

        validateSwitchedShunts(model.getSwitchedShunts(), buses, psseVersion);
    }

    private static Map<Integer, List<Integer>> generateBuses(List<PsseBus> psseBuses) {
        Map<Integer, List<Integer>> buses = new HashMap<>();
        for (int i = 0; i < psseBuses.size(); i++) {
            buses.computeIfAbsent(psseBuses.get(i).getI(), k -> new ArrayList<>()).add(i);
        }
        return buses;
    }

    private void validateCaseIdentification(PsseCaseIdentification caseIdentification) {
        if (caseIdentification.getSbase() <= 0.0) {
            validationErrors.add(String.format(Locale.US, "CaseIdentification: Unexpected Sbase: %.2f", caseIdentification.getSbase()));
            validCase = false;
        }
        if (caseIdentification.getBasfrq() <= 0.0) {
            validationErrors.add(String.format(Locale.US, "CaseIdentification: Unexpected Basfrq: %.2f", caseIdentification.getBasfrq()));
            validCase = false;
        }
    }

    private void validateBuses(List<PsseBus> psseBuses, Map<Integer, List<Integer>> buses) {
        for (Map.Entry<Integer, List<Integer>> entry : buses.entrySet()) {
            if (entry.getValue().size() != 1) {
                validationErrors.add(String.format("Bus: %d defined multiple times (%d)", entry.getKey(), entry.getValue().size()));
                validCase = false;
            }
        }

        for (PsseBus psseBus : psseBuses) {
            if (psseBus.getI() < 1 || psseBus.getI() > 999997) {
                validationErrors.add(String.format("Bus: Unexpected I: %d", psseBus.getI()));
                validCase = false;
            }
            if (psseBus.getBaskv() < 0.0) {
                validationErrors.add(String.format(Locale.US, "Bus: %d Unexpected Baskv: %.2f", psseBus.getI(), psseBus.getBaskv()));
                validCase = false;
            }
        }
    }

    private void validateLoads(List<PsseLoad> loads, Map<Integer, List<Integer>> buses) {
        Map<String, List<String>> busesLoads = new HashMap<>();

        for (PsseLoad load : loads) {
            if (!buses.containsKey(load.getI())) {
                validationWarnings.add(String.format("Load: bus not found I: %d, Load record %d, %s, ... will be ignored", load.getI(), load.getI(), load.getId()));
                continue;
            }
            addBusesMap(busesLoads, load.getI(), load.getId());
        }

        checkDuplicates("Load", "loads", getDuplicates(busesLoads));
    }

    private void validateFixedShunts(List<PsseFixedShunt> fixedShunts, Map<Integer, List<Integer>> buses) {
        Map<String, List<String>> busesFixedShunts = new HashMap<>();

        for (PsseFixedShunt fixedShunt : fixedShunts) {
            if (!buses.containsKey(fixedShunt.getI())) {
                validationWarnings.add(String.format("FixedShunt: bus not found I: %d, FixedShunt record %d, %s, ... will be ignored", fixedShunt.getI(), fixedShunt.getI(), fixedShunt.getId()));
                continue;
            }
            addBusesMap(busesFixedShunts, fixedShunt.getI(), fixedShunt.getId());
        }

        checkDuplicates("FixedShunt", "fixed shunts", getDuplicates(busesFixedShunts));
    }

    private void validateGenerators(List<PsseBus> psseBuses, List<PsseGenerator> generators, Map<Integer, List<Integer>> buses) {

        Map<String, List<String>> busesGenerators = new HashMap<>();

        for (PsseGenerator generator : generators) {
            if (!buses.containsKey(generator.getI())) {
                validationWarnings.add(String.format("Generator: bus not found I: %d, Generator record %d, %s, ... will be ignored", generator.getI(), generator.getI(), generator.getId()));
                continue;
            }
            if (generator.getQt() < generator.getQb()) {
                validationErrors.add(String.format(Locale.US, "Generator: %d %s Unexpected Qmin: %.2f Qmax: %.2f", generator.getI(), generator.getId(), generator.getQb(), generator.getQt()));
                validCase = false;
            }
            if (generator.getIreg() != 0 && !buses.containsKey(generator.getIreg())) {
                validationErrors.add(String.format("Generator: %d %s Unexpected IReg: %d", generator.getI(), generator.getId(), generator.getIreg()));
                validCase = false;
            }
            if (generator.getPt() < generator.getPb()) {
                validationErrors.add(String.format(Locale.US, "Generator: %d %s Unexpected Pmin: %.2f Pmax: %.2f", generator.getI(), generator.getId(), generator.getPb(), generator.getPt()));
                validCase = false;
            }
            validateGeneratorRegulatingBus(psseBuses, buses, generator);

            addBusesMap(busesGenerators, generator.getI(), generator.getId());
        }

        checkDuplicates("Generator", "generators", getDuplicates(busesGenerators));
    }

    private void validateGeneratorRegulatingBus(List<PsseBus> psseBuses, Map<Integer, List<Integer>> buses, PsseGenerator generator) {
        PsseBus regulatingBus = getRegulatingBus(psseBuses, buses, generator.getIreg(), generator.getI());
        if (regulatingBus != null
            && (regulatingBus.getIde() == 2 || regulatingBus.getIde() == 3)
            && generator.getVs() <= 0.0) {
            validationErrors.add(String.format(Locale.US, "Generator: %d %s Unexpected Voltage setpoint: %.2f", generator.getI(), generator.getId(), generator.getVs()));
            validCase = false;
        }
    }

    private void validateNonTransformerBranches(List<PsseNonTransformerBranch> nonTransformerBranches, Map<Integer, List<Integer>> buses) {
        Map<String, List<String>> busesNonTransformerBranches = new HashMap<>();

        for (PsseNonTransformerBranch nonTransformerBranch : nonTransformerBranches) {
            if (isNonTransformedBranchBadlyConnected(nonTransformerBranch, buses)) {
                continue;
            }
            if (nonTransformerBranch.getX() == 0.0) {
                validationErrors.add(String.format(Locale.US, "NonTransformerBranch: %d %d %s Unexpected X: %.5f", nonTransformerBranch.getI(), nonTransformerBranch.getJ(), nonTransformerBranch.getCkt(), nonTransformerBranch.getX()));
                validCase = false;
            }
            addBusesMap(busesNonTransformerBranches, nonTransformerBranch.getI(), nonTransformerBranch.getJ(), nonTransformerBranch.getCkt());
        }

        checkDuplicatesLinks("NonTransformerBranch", "branches", getDuplicates(busesNonTransformerBranches));
    }

    private boolean isNonTransformedBranchBadlyConnected(PsseNonTransformerBranch nonTransformerBranch, Map<Integer, List<Integer>> buses) {
        if (!buses.containsKey(nonTransformerBranch.getI())) {
            validationWarnings.add(String.format("NonTransformerBranch: bus not found I: %d, NonTransformerBranch record %d, %d, %s, ... will be ignored", nonTransformerBranch.getI(), nonTransformerBranch.getI(), nonTransformerBranch.getJ(), nonTransformerBranch.getCkt()));
            return true;
        }
        if (!buses.containsKey(nonTransformerBranch.getJ())) {
            validationWarnings.add(String.format("NonTransformerBranch: bus not found J: %d, NonTransformerBranch record %d, %d, %s, ... will be ignored", nonTransformerBranch.getJ(), nonTransformerBranch.getI(), nonTransformerBranch.getJ(), nonTransformerBranch.getCkt()));
            return true;
        }
        return false;
    }

    private void validateTransformers(List<PsseTransformer> transformers, Map<Integer, List<Integer>> buses) {
        List<PsseTransformer> twoWindingsTransformers = transformers.parallelStream()
            .filter(transformer -> transformer.getK() == 0).toList();
        validateTwoWindingsTransformers(twoWindingsTransformers, buses);

        List<PsseTransformer> threeWindingsTransformers = transformers.parallelStream()
            .filter(transformer -> transformer.getK() != 0).toList();
        validateThreeWindingsTransformers(threeWindingsTransformers, buses);
    }

    private void validateTwoWindingsTransformers(List<PsseTransformer> transformers, Map<Integer, List<Integer>> buses) {
        Map<String, List<String>> busesTransformers = new HashMap<>();

        for (PsseTransformer transformer : transformers) {
            if (isT2wTransformerBadlyConnected(transformer, buses)) {
                continue;
            }

            String id = String.format("%d %d %s", transformer.getI(), transformer.getJ(), transformer.getCkt());
            validateTransformerX(id, transformer.getX12(), "X12");
            validateTransformerRatio(id, transformer.getWinding1().getWindv(), "ratio");
            validateTransformerSbase(id, transformer.getCz(), transformer.getCm(), transformer.getSbase12(), "sbase12");
            validateTransformerWindingVmiVma(id, transformer.getWinding1().getCod(), transformer.getWinding1().getVmi(), transformer.getWinding1().getVma(), "winding1 Vmi Vma");
            validateTransformerWindingRmiRma(id, transformer.getWinding1().getCod(), transformer.getWinding1().getRmi(), transformer.getWinding1().getRma(), "winding1 Rmi Rma");
            validateTransformerWindingCont(buses, id, transformer.getWinding1().getCod(), transformer.getWinding1().getCont(), "winding1 Cont");

            addBusesMap(busesTransformers, transformer.getI(), transformer.getJ(), transformer.getCkt());
        }

        checkDuplicatesLinks("Transformer", "branches", getDuplicates(busesTransformers));
    }

    private boolean isT2wTransformerBadlyConnected(PsseTransformer transformer, Map<Integer, List<Integer>> buses) {
        if (!buses.containsKey(transformer.getI())) {
            validationWarnings.add(String.format("Transformer: bus not found I: %d, Transformer record %d, %d, %s, ... will be ignored", transformer.getI(), transformer.getI(), transformer.getJ(), transformer.getCkt()));
            return true;
        }
        if (!buses.containsKey(transformer.getJ())) {
            validationWarnings.add(String.format("Transformer: bus not found J: %d, Transformer record %d, %d, %s, ... will be ignored", transformer.getJ(), transformer.getI(), transformer.getJ(), transformer.getCkt()));
            return true;
        }
        return false;
    }

    private void validateThreeWindingsTransformers(List<PsseTransformer> transformers, Map<Integer, List<Integer>> buses) {
        Map<String, List<String>> busesTransformers = new HashMap<>();

        for (PsseTransformer transformer : transformers) {
            if (isT3wTransformerBadlyConnected(transformer, buses)) {
                continue;
            }

            String id = String.format("%d %d %d %s", transformer.getI(), transformer.getJ(), transformer.getK(), transformer.getCkt());
            validateTransformerX(id, transformer.getX12(), "X12");
            validateTransformerX(id, transformer.getX31(), "X31");
            validateTransformerX(id, transformer.getX23(), "X23");

            validateTransformerRatio(id, transformer.getWinding1().getWindv(), "winding1 ratio");
            validateTransformerRatio(id, transformer.getWinding2().getWindv(), "winding2 ratio");
            validateTransformerRatio(id, transformer.getWinding3().getWindv(), "winding3 ratio");

            validateTransformerSbase(id, transformer.getCz(), transformer.getCm(), transformer.getSbase12(), "sbase12");
            validateTransformerSbase(id, transformer.getCz(), transformer.getCm(), transformer.getSbase23(), "sbase23");
            validateTransformerSbase(id, transformer.getCz(), transformer.getCm(), transformer.getSbase31(), "sbase31");

            validateTransformerWindingVmiVma(id, transformer.getWinding1().getCod(), transformer.getWinding1().getVmi(), transformer.getWinding1().getVma(), "winding1 Vmi Vma");
            validateTransformerWindingVmiVma(id, transformer.getWinding2().getCod(), transformer.getWinding2().getVmi(), transformer.getWinding2().getVma(), "winding2 Vmi Vma");
            validateTransformerWindingVmiVma(id, transformer.getWinding3().getCod(), transformer.getWinding3().getVmi(), transformer.getWinding3().getVma(), "winding3 Vmi Vma");

            validateTransformerWindingRmiRma(id, transformer.getWinding1().getCod(), transformer.getWinding1().getRmi(), transformer.getWinding1().getRma(), "winding1 Rmi Rma");
            validateTransformerWindingRmiRma(id, transformer.getWinding2().getCod(), transformer.getWinding2().getRmi(), transformer.getWinding2().getRma(), "winding2 Rmi Rma");
            validateTransformerWindingRmiRma(id, transformer.getWinding3().getCod(), transformer.getWinding3().getRmi(), transformer.getWinding3().getRma(), "winding3 Rmi Rma");

            validateTransformerWindingCont(buses, id, transformer.getWinding1().getCod(), transformer.getWinding1().getCont(), "winding1 Cont");
            validateTransformerWindingCont(buses, id, transformer.getWinding2().getCod(), transformer.getWinding2().getCont(), "winding2 Cont");
            validateTransformerWindingCont(buses, id, transformer.getWinding3().getCod(), transformer.getWinding3().getCont(), "winding3 Cont");

            addBusesMap(busesTransformers, transformer.getI(), transformer.getJ(), transformer.getK(), transformer.getCkt());
        }

        Map<String, List<String>> duplicatedBusesTransformers = getDuplicates(busesTransformers);
        if (!duplicatedBusesTransformers.isEmpty()) {
            duplicatedBusesTransformers.forEach((key,
                value) -> validationErrors.add(String.format(
                    "Transformer: Multiple branches (%d) between buses %d, %d and %d with the same Id %s",
                    value.size(), firstBus(key), secondBus(key), thirdBus(key), value.get(0))));
            validCase = false;
        }
    }

    private boolean isT3wTransformerBadlyConnected(PsseTransformer transformer, Map<Integer, List<Integer>> buses) {
        if (!buses.containsKey(transformer.getI())) {
            validationWarnings.add(String.format("Transformer: bus not found I: %d, Transformer record %d, %d, %d, %s, ... will be ignored", transformer.getI(), transformer.getI(), transformer.getJ(), transformer.getK(), transformer.getCkt()));
            return true;
        }
        if (!buses.containsKey(transformer.getJ())) {
            validationWarnings.add(String.format("Transformer: bus not found J: %d, Transformer record %d, %d, %d, %s, ... will be ignored", transformer.getJ(), transformer.getI(), transformer.getJ(), transformer.getK(), transformer.getCkt()));
            return true;
        }
        if (!buses.containsKey(transformer.getK())) {
            validationWarnings.add(String.format("Transformer: bus not found K: %d, Transformer record %d, %d, %d, %s, ... will be ignored", transformer.getK(), transformer.getI(), transformer.getJ(), transformer.getK(), transformer.getCkt()));
            return true;
        }
        return false;
    }

    private void validateTransformerX(String id, double x, String xTag) {
        if (x == 0.0) {
            validationErrors.add(getErrorTransformer1Parameter(id, xTag, x));
            validCase = false;
        }
    }

    private void validateTransformerRatio(String id, double ratio, String ratioTag) {
        if (ratio <= 0.0) {
            validationErrors.add(getErrorTransformer1Parameter(id, ratioTag, ratio));
            validCase = false;
        }
    }

    private void validateTransformerSbase(String id, int cz, int cm, double sbase, String sbaseTag) {
        if ((cz == 2 || cz == 3 || cm == 2) && sbase <= 0.0) {
            validationErrors.add(getErrorTransformer1Parameter(id, sbaseTag, sbase));
            validCase = false;
        }
    }

    private void validateTransformerWindingVmiVma(String id, int cod, double windingVmi, double windingVma, String windingVmiVmaTag) {
        if (Math.abs(cod) == 1 && (windingVmi <= 0.0 || windingVma <= 0.0 || windingVma < windingVmi)) {
            validationErrors.add(getErrorTransformer2Parameters(id, windingVmiVmaTag, windingVmi, windingVma));
            validCase = false;
        }
        if ((Math.abs(cod) == 2 || Math.abs(cod) == 3 || Math.abs(cod) == 5) && windingVma < windingVmi) {
            validationErrors.add(getErrorTransformer2Parameters(id, windingVmiVmaTag, windingVmi, windingVma));
            validCase = false;
        }
    }

    private void validateTransformerWindingRmiRma(String id, int cod, double windingRmi, double windingRma, String windingRmiRmaTag) {
        if ((Math.abs(cod) == 1 || Math.abs(cod) == 2) && (windingRmi <= 0.0 || windingRma <= 0.0 || windingRma < windingRmi)) {
            validationErrors.add(getErrorTransformer2Parameters(id, windingRmiRmaTag, windingRmi, windingRma));
            validCase = false;
        }
        if ((Math.abs(cod) == 3 || Math.abs(cod) == 5) && windingRma < windingRmi) {
            validationErrors.add(getErrorTransformer2Parameters(id, windingRmiRmaTag, windingRmi, windingRma));
            validCase = false;
        }
    }

    private void validateTransformerWindingCont(Map<Integer, List<Integer>> buses, String id, int cod, int windingCont, String windingContTag) {
        if (Math.abs(cod) == 1 && (windingCont == 0 || !buses.containsKey(Math.abs(windingCont)))) {
            validationErrors.add(String.format(Locale.US, "Transformer: %s Unexpected %s: %d", id, windingContTag, windingCont));
            validCase = false;
        }
    }

    private void validateTwoTerminalDcTransmissionLines(List<PsseTwoTerminalDcTransmissionLine> twoTerminalDcTransmissionLines, Map<Integer, List<Integer>> buses) {
        Map<String, Integer> twoTerminalDcNames = new HashMap<>();
        for (PsseTwoTerminalDcTransmissionLine twoTerminalDc : twoTerminalDcTransmissionLines) {
            if (isTwoTerminalDcTransmissionLineBadlyConnected(twoTerminalDc, buses)) {
                continue;
            }
            twoTerminalDcNames.put(twoTerminalDc.getName(), twoTerminalDcNames.getOrDefault(twoTerminalDc.getName(), 0) + 1);
        }
        List<String> duplicatedNames = twoTerminalDcNames.keySet().stream().filter(key -> twoTerminalDcNames.get(key) > 1).toList();
        if (!duplicatedNames.isEmpty()) {
            duplicatedNames.forEach(name -> validationErrors.add(String.format("TwoTerminalDcTransmissionLine: This name %s is not unique", name)));
            validCase = false;
        }
    }

    private boolean isTwoTerminalDcTransmissionLineBadlyConnected(PsseTwoTerminalDcTransmissionLine twoTerminalDc, Map<Integer, List<Integer>> buses) {
        if (!buses.containsKey(twoTerminalDc.getRectifier().getIp())) {
            validationWarnings.add(String.format("TwoTerminalDcTransmissionLine: %s rectifier bus not found Ip: %d, TwoTerminalDcTransmissionLine record %s, ... will be ignored", twoTerminalDc.getName(), twoTerminalDc.getRectifier().getIp(), twoTerminalDc.getName()));
            return true;
        }
        if (!buses.containsKey(twoTerminalDc.getInverter().getIp())) {
            validationWarnings.add(String.format("TwoTerminalDcTransmissionLine: %s inverter bus not found Ip: %d, TwoTerminalDcTransmissionLine record %s, ... will be ignored", twoTerminalDc.getName(), twoTerminalDc.getInverter().getIp(), twoTerminalDc.getName()));
            return true;
        }
        return false;
    }

    private void validateSwitchedShunts(List<PsseSwitchedShunt> switchedShunts, Map<Integer, List<Integer>> buses, PsseVersion psseVersion) {
        Map<String, List<String>> busesSwitchedShunts = new HashMap<>();

        for (PsseSwitchedShunt switchedShunt : switchedShunts) {
            if (!buses.containsKey(switchedShunt.getI())) {
                validationWarnings.add(String.format("SwitchedShunt: bus not found I: %d, SwitchedShunt record %s, ... will be ignored", switchedShunt.getI(), switchedShuntId(switchedShunt, psseVersion)));
                continue;
            }
            String id = switchedShuntId(switchedShunt, psseVersion);
            int regulatingBus = switchedShuntRegulatingBus(switchedShunt, psseVersion);
            if (switchedShunt.getModsw() != 0 && regulatingBus != 0 && !buses.containsKey(regulatingBus)) {
                validationErrors.add(String.format("SwitchedShunt: %s Unexpected Swrem/Swreg: %d", id, regulatingBus));
                validCase = false;
            }
            if (switchedShunt.getModsw() != 0 && switchedShunt.getVswhi() < switchedShunt.getVswlo()) {
                validationErrors.add(String.format(Locale.US, "SwitchedShunt: %s Unexpected Vswlo Vswhi: %.5f %.5f", id, switchedShunt.getVswlo(), switchedShunt.getVswhi()));
                validCase = false;
            }
            if ((switchedShunt.getModsw() == 1 || switchedShunt.getModsw() == 2) && (switchedShunt.getVswlo() <= 0.0 || switchedShunt.getVswhi() <= 0.0)) {
                validationErrors.add(String.format(Locale.US, "SwitchedShunt: %s Unexpected Vswlo Vswhi: %.5f %.5f", id, switchedShunt.getVswlo(), switchedShunt.getVswhi()));
                validCase = false;
            }
            addSwitchedShuntBusesMap(busesSwitchedShunts, switchedShunt, psseVersion);
        }

        Map<String, List<String>> duplicatedBusesSwitchedShunts = getDuplicates(busesSwitchedShunts);
        if (!duplicatedBusesSwitchedShunts.isEmpty()) {
            duplicatedBusesSwitchedShunts.forEach((key, value) -> validationErrors.add(multipleSwitchedShuntString(key, value, psseVersion)));
            validCase = false;
        }
    }

    static String switchedShuntId(PsseSwitchedShunt switchedShunt, PsseVersion psseVersion) {
        if (psseVersion.major() == V35) {
            return String.format("%d %s", switchedShunt.getI(), switchedShunt.getId());
        } else {
            return String.format("%d", switchedShunt.getI());
        }
    }

    static int switchedShuntRegulatingBus(PsseSwitchedShunt switchedShunt, PsseVersion psseVersion) {
        if (psseVersion.major() == V35) {
            return switchedShunt.getSwreg();
        } else {
            return switchedShunt.getSwrem();
        }
    }

    private static void addSwitchedShuntBusesMap(Map<String, List<String>> busesSwitchedShunts, PsseSwitchedShunt switchedShunt, PsseVersion psseVersion) {
        if (psseVersion.major() == V35) {
            addBusesMap(busesSwitchedShunts, switchedShunt.getI(), switchedShunt.getId());
        } else {
            addBusesMap(busesSwitchedShunts, switchedShunt.getI(), "1");
        }
    }

    private static String multipleSwitchedShuntString(String key, List<String> value, PsseVersion psseVersion) {
        if (psseVersion.major() == V35) {
            return String.format("SwitchedShunt: Multiple fixed shunts (%d) at bus %d with the same Id %s", value.size(), Integer.valueOf(key), value.get(0));
        } else {
            return String.format("SwitchedShunt: Multiple fixed shunts (%d) at bus %d", value.size(), Integer.valueOf(key));
        }
    }

    private static PsseBus getRegulatingBus(List<PsseBus> psseBuses, Map<Integer, List<Integer>> buses, int ireg, int i) {
        int regulatingId = i;
        if (ireg != 0) {
            regulatingId = ireg;
        }

        if (buses.containsKey(regulatingId)) {
            return psseBuses.get(buses.get(regulatingId).get(0));
        }

        return null;
    }

    private static Map<String, List<String>> getDuplicates(Map<String, List<String>> busesMap) {
        Map<String, List<String>> duplicatedBusMap = new HashMap<>();

        busesMap.forEach((key, value) -> value.stream().collect(Collectors.groupingBy(s -> s))
            .entrySet()
            .stream()
            .filter(e -> e.getValue().size() > 1)
            .forEach(e -> duplicatedBusMap.put(key, e.getValue())));

        return duplicatedBusMap;
    }

    private static void addBusesMap(Map<String, List<String>> busesMap, int busI, String id) {
        String busString = String.format("%06d", busI);
        busesMap.computeIfAbsent(busString, k -> new ArrayList<>()).add(id);
    }

    private static void addBusesMap(Map<String, List<String>> busesMap, int busI, int busJ, String ckt) {
        String busString;
        if (busI < busJ) {
            busString = String.format("%06d-%06d", busI, busJ);
        } else {
            busString = String.format("%06d-%06d", busJ, busI);
        }

        busesMap.computeIfAbsent(busString, k -> new ArrayList<>()).add(ckt);
    }

    private static void addBusesMap(Map<String, List<String>> busesMap, int busI, int busJ, int busK, String ckt) {
        String busString;
        List<Integer> buses = new ArrayList<>();
        buses.add(busI);
        buses.add(busJ);
        buses.add(busK);
        Collections.sort(buses);
        busString = String.format("%06d-%06d-%06d", buses.get(0), buses.get(1), buses.get(2));

        busesMap.computeIfAbsent(busString, k -> new ArrayList<>()).add(ckt);
    }

    private static int firstBus(String busKey) {
        String[] tokens = busKey.split("-");
        return Integer.parseInt(tokens[0]);
    }

    private static int secondBus(String busKey) {
        String[] tokens = busKey.split("-");
        return Integer.parseInt(tokens[1]);
    }

    private static int thirdBus(String busKey) {
        String[] tokens = busKey.split("-");
        return Integer.parseInt(tokens[2]);
    }

    private void checkDuplicates(String tag, String tagEquipments, Map<String, List<String>> duplicatedBusesEquipments) {
        if (!duplicatedBusesEquipments.isEmpty()) {
            duplicatedBusesEquipments.forEach((key, value) -> validationErrors
                .add(String.format("%s: Multiple %s (%d) at bus %d with the same Id %s", tag, tagEquipments,
                    value.size(), Integer.valueOf(key), value.get(0))));
            validCase = false;
        }
    }

    private void checkDuplicatesLinks(String tag, String tagLinks, Map<String, List<String>> duplicatedBusesLinks) {
        if (!duplicatedBusesLinks.isEmpty()) {
            duplicatedBusesLinks.forEach((key, value) -> validationErrors
                .add(String.format("%s: Multiple %s (%d) between buses %d and %d with the same Id %s", tag, tagLinks,
                    value.size(), firstBus(key), secondBus(key), value.get(0))));
            validCase = false;
        }
    }

    private String getErrorTransformer1Parameter(String id, String tag, double param) {
        return String.format(Locale.US, ERROR_TRANSFORMER_1_PARAMETER, id, tag, param);
    }

    private String getErrorTransformer2Parameters(String id, String tag, double param1, double param2) {
        return String.format(Locale.US, ERROR_TRANSFORMER_2_PARAMETERS, id, tag, param1, param2);
    }
}