ContextExport.java

/**
 * Copyright (c) 2024, 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 com.powsybl.iidm.network.Bus;
import com.powsybl.iidm.network.DanglingLine;
import com.powsybl.iidm.network.IdentifiableType;
import com.powsybl.iidm.network.VoltageLevel;
import com.powsybl.psse.model.PsseException;
import com.powsybl.psse.model.pf.PsseSubstation;

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

/**
 * @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
 * @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
 */
final class ContextExport {
    private final boolean isFullExport;
    private final UpdateExport updateExport;
    private final FullExport fullExport;

    ContextExport(boolean isFullExport) {
        this.isFullExport = isFullExport;
        this.updateExport = new UpdateExport();
        this.fullExport = new FullExport();
    }

    boolean isFullExport() {
        return this.isFullExport;
    }

    UpdateExport getUpdateExport() {
        return this.updateExport;
    }

    FullExport getFullExport() {
        return this.fullExport;
    }

    static class UpdateExport {
        private final Map<Integer, Bus> busIBusView;
        private final Map<String, Bus> voltageLevelNodeIdBusView;
        private final Map<VoltageLevel, PsseSubstation> voltageLevelPsseSubstation;

        UpdateExport() {
            this.busIBusView = new HashMap<>();
            this.voltageLevelNodeIdBusView = new HashMap<>();
            this.voltageLevelPsseSubstation = new HashMap<>();
        }

        void addBusIBusView(int busI, Bus busView) {
            this.busIBusView.put(busI, busView);
        }

        Optional<Bus> getBusView(int busI) {
            return this.busIBusView.containsKey(busI) ? Optional.of(this.busIBusView.get(busI)) : Optional.empty();
        }

        // busView can be null
        void addNodeBusView(VoltageLevel voltageLevel, int node, Bus busView) {
            this.voltageLevelNodeIdBusView.put(AbstractConverter.getNodeId(voltageLevel, node), busView);
        }

        Optional<Bus> getBusView(VoltageLevel voltageLevel, int node) {
            return Optional.ofNullable(this.voltageLevelNodeIdBusView.get(AbstractConverter.getNodeId(voltageLevel, node)));
        }

        void addVoltageLevelPsseSubstation(VoltageLevel voltageLevel, PsseSubstation psseSubstation) {
            voltageLevelPsseSubstation.put(voltageLevel, psseSubstation);
        }

        Optional<PsseSubstation> getPsseSubstation(VoltageLevel voltageLevel) {
            return Optional.ofNullable(voltageLevelPsseSubstation.get(voltageLevel));
        }
    }

    static class FullExport {
        private int maxPsseBus;
        private int maxPsseSubstation;
        private final Map<Bus, Integer> busViewBusI;
        private final Map<Integer, Bus> busIBusView;
        private final Map<DanglingLine, Integer> danglingLineBusI;
        private final Map<String, NodeData> voltageLevelNodeIdNodeData;
        private final Map<String, Integer> psseSubstationIdLastPsseNode;
        private final Map<String, Set<VoltageLevel>> psseSubstationIdVoltageLevels;
        private final Set<VoltageLevel> voltageLevelsExportedAsNodeBreaker;
        private final Map<String, String> equipmentIdCkt;
        private final Map<String, Integer> equipmentBusesIdMaxCkt;

        FullExport() {
            this.maxPsseBus = 0;
            this.maxPsseSubstation = 0;
            this.busViewBusI = new HashMap<>();
            this.busIBusView = new HashMap<>();
            this.danglingLineBusI = new HashMap<>();
            this.voltageLevelNodeIdNodeData = new HashMap<>();
            this.psseSubstationIdLastPsseNode = new HashMap<>();
            this.psseSubstationIdVoltageLevels = new HashMap<>();
            this.voltageLevelsExportedAsNodeBreaker = new HashSet<>();
            this.equipmentIdCkt = new HashMap<>();
            this.equipmentBusesIdMaxCkt = new HashMap<>();
        }

        int getNewPsseBusI() {
            return ++maxPsseBus;
        }

        int getNewPsseSubstationIs() {
            return ++maxPsseSubstation;
        }

        void addBusIBusView(int busI, Bus busView) {
            this.busIBusView.put(busI, busView);
            if (busView != null) {
                this.busViewBusI.put(busView, busI);
            }
        }

        void addDanglingLineBusI(DanglingLine danglingLine, int busI) {
            this.danglingLineBusI.put(danglingLine, busI);
        }

        Set<Integer> getBusISet() {
            return busIBusView.keySet();
        }

        OptionalInt getBusI(Bus busView) {
            return Optional.ofNullable(this.busViewBusI.get(busView)).map(OptionalInt::of).orElse(OptionalInt.empty());
        }

        OptionalInt getBusI(DanglingLine danglingLine) {
            return Optional.ofNullable(this.danglingLineBusI.get(danglingLine)).map(OptionalInt::of).orElse(OptionalInt.empty());
        }

        Optional<Bus> getBusView(int busI) {
            return Optional.ofNullable(this.busIBusView.get(busI));
        }

        // voltageBusViewBus can be null
        void addNodeData(VoltageLevel voltageLevel, int node, int psseBusI, int psseNode, Bus voltageBusViewBus) {
            this.voltageLevelNodeIdNodeData.put(AbstractConverter.getNodeId(voltageLevel, node), new NodeData(voltageLevel, node, psseBusI, psseNode, voltageBusViewBus));
        }

        OptionalInt getBusI(VoltageLevel voltageLevel, int node) {
            String voltageLevelNodeId = AbstractConverter.getNodeId(voltageLevel, node);
            return Optional.ofNullable(this.voltageLevelNodeIdNodeData.get(voltageLevelNodeId))
                    .map(nodeData -> OptionalInt.of(nodeData.psseBusI))
                    .orElse(OptionalInt.empty());
        }

        OptionalInt getPsseNode(VoltageLevel voltageLevel, int node) {
            String voltageLevelNodeId = AbstractConverter.getNodeId(voltageLevel, node);
            return Optional.ofNullable(this.voltageLevelNodeIdNodeData.get(voltageLevelNodeId))
                    .map(nodeData -> OptionalInt.of(nodeData.psseNode))
                    .orElse(OptionalInt.empty());
        }

        Optional<Bus> getVoltageBus(VoltageLevel voltageLevel, int node) {
            String voltageLevelNodeId = AbstractConverter.getNodeId(voltageLevel, node);
            return Optional.ofNullable(this.voltageLevelNodeIdNodeData.get(voltageLevelNodeId)).map(nodeData -> nodeData.voltageBusViewBus);
        }

        boolean isDeEnergized(VoltageLevel voltageLevel, int node) {
            return getVoltageBus(voltageLevel, node).map(bus -> bus.getV() == 0.0 && bus.getAngle() == 0.0).orElse(true);
        }

        Optional<VoltageLevel> getVoltageLevel(int busI) {
            return this.voltageLevelNodeIdNodeData.values().stream().filter(nodeData -> nodeData.psseBusI == busI).map(nodeData -> nodeData.voltageLevel).findFirst();
        }

        OptionalInt getNode(int busI) {
            return this.voltageLevelNodeIdNodeData.values().stream().filter(nodeData -> nodeData.psseBusI == busI).mapToInt(nodeData -> nodeData.node).findFirst();
        }

        private record NodeData(VoltageLevel voltageLevel, int node, int psseBusI, int psseNode, Bus voltageBusViewBus) {
        }

        int getNewPsseNode(String psseSubstationId) {
            int newPsseNode = getLastPsseNode(psseSubstationId) + 1;
            this.psseSubstationIdLastPsseNode.put(psseSubstationId, newPsseNode);
            return newPsseNode;
        }

        int getLastPsseNode(String psseSubstationId) {
            return this.psseSubstationIdLastPsseNode.getOrDefault(psseSubstationId, 0);
        }

        void addPsseSubstationIdVoltageLevel(String psseSubstationId, VoltageLevel voltageLevel) {
            this.psseSubstationIdVoltageLevels.computeIfAbsent(psseSubstationId, k -> new HashSet<>()).add(voltageLevel);
            this.voltageLevelsExportedAsNodeBreaker.add(voltageLevel);
        }

        List<String> getSortedPsseSubstationIds() {
            return this.psseSubstationIdVoltageLevels.keySet().stream().sorted().toList();
        }

        Set<VoltageLevel> getVoltageLevelSet(String psseSubstationId) {
            return this.psseSubstationIdVoltageLevels.getOrDefault(psseSubstationId, Collections.emptySet());
        }

        boolean isExportedAsNodeBreaker(VoltageLevel voltageLevel) {
            return this.voltageLevelsExportedAsNodeBreaker.contains(voltageLevel);
        }

        String getEquipmentCkt(String equipmentId, String psseEquipmentType, int busI) {
            return getEquipmentCkt(null, equipmentId, psseEquipmentType, busI, 0, 0);
        }

        // There is no psseEquipmentType for the switches. The assigned string type cannot be a PsseEquipmentType
        String getEquipmentCkt(VoltageLevel voltageLevel, String equipmentId, int busI, int busJ) {
            return getEquipmentCkt(voltageLevel, equipmentId, IdentifiableType.SWITCH.name(), busI, busJ, 0);
        }

        String getEquipmentCkt(String equipmentId, String psseEquipmentType, int busI, int busJ) {
            return getEquipmentCkt(null, equipmentId, psseEquipmentType, busI, busJ, 0);
        }

        String getEquipmentCkt(String equipmentId, String psseEquipmentType, int busI, int busJ, int busK) {
            return getEquipmentCkt(null, equipmentId, psseEquipmentType, busI, busJ, busK);
        }

        private String getEquipmentCkt(VoltageLevel voltageLevel, String equipmentId, String psseEquipmentType, int busI, int busJ, int busK) {
            if (equipmentIdCkt.containsKey(equipmentId)) {
                return equipmentIdCkt.get(equipmentId);
            }
            String equipmentBusesId = getEquipmentBusesId(equipmentId, getVoltageLevelType(voltageLevel, psseEquipmentType), busI, busJ, busK);
            int cktInteger = getNewCkt(equipmentBusesId);
            String cktString = String.format("%02d", cktInteger);
            addCkt(equipmentId, equipmentBusesId, cktInteger, cktString);
            return cktString;
        }

        private void addCkt(String equipmentId, String equipmentBusesId, int maxCkt, String ckt) {
            equipmentBusesIdMaxCkt.put(equipmentBusesId, maxCkt);
            equipmentIdCkt.put(equipmentId, ckt);
        }

        private int getNewCkt(String equipmentBusesId) {
            return equipmentBusesIdMaxCkt.containsKey(equipmentBusesId) ? equipmentBusesIdMaxCkt.get(equipmentBusesId) + 1 : 1;
        }

        // switches must be unique inside the voltageLevel
        private static String getVoltageLevelType(VoltageLevel voltageLevel, String psseEquipmentType) {
            return voltageLevel != null ? voltageLevel.getId() + "-" + psseEquipmentType : psseEquipmentType;
        }

        private static String getEquipmentBusesId(String equipmentId, String type, int busI, int busJ, int busK) {
            List<Integer> sortedBuses = Stream.of(busI, busJ, busK).filter(bus -> bus != 0).sorted().toList();
            if (sortedBuses.size() == 1) {
                return type + "-" + sortedBuses.get(0);
            } else if (sortedBuses.size() == 2) {
                return type + "-" + sortedBuses.get(0) + "-" + sortedBuses.get(1);
            } else if (sortedBuses.size() == 3) {
                return type + "-" + sortedBuses.get(0) + "-" + sortedBuses.get(1) + "-" + sortedBuses.get(2);
            } else {
                throw new PsseException("All the buses are zero. EquipmentId: " + equipmentId);
            }
        }
    }
}