Context.java

/**
 * Copyright (c) 2019, 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.cgmes.conversion;

import com.powsybl.cgmes.conversion.Conversion.Config;
import com.powsybl.cgmes.conversion.elements.hvdc.DcMapping;
import com.powsybl.cgmes.conversion.naming.NamingStrategy;
import com.powsybl.cgmes.model.CgmesModel;
import com.powsybl.cgmes.model.CgmesNames;
import com.powsybl.cgmes.model.PowerFlow;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.iidm.network.IdentifiableType;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.Terminal;
import com.powsybl.triplestore.api.PropertyBags;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
 */
public class Context {

    public Context(CgmesModel cgmes, Config config, Network network) {
        this(cgmes, config, network, ReportNode.NO_OP);
    }

    public Context(CgmesModel cgmes, Config config, Network network, ReportNode reportNode) {
        this.cgmes = Objects.requireNonNull(cgmes);
        this.config = Objects.requireNonNull(config);
        this.network = Objects.requireNonNull(network);
        pushReportNode(Objects.requireNonNull(reportNode));

        // Even if the CGMES model is node-breaker,
        // we could decide to ignore the connectivity nodes and
        // create buses directly from topological nodes,
        // the configuration says if we are performing the conversion
        // based on existing node-breaker info
        nodeBreaker = cgmes.isNodeBreaker() && !config.importNodeBreakerAsBusBreaker();

        namingStrategy = config.getNamingStrategy();
        cgmesBoundary = new CgmesBoundary(cgmes);
        nodeContainerMapping = new NodeContainerMapping(this);
        terminalMapping = new TerminalMapping();
        dcMapping = new DcMapping(this);
        loadingLimitsMapping = new LoadingLimitsMapping(this);
        regulatingControlMapping = new RegulatingControlMapping(this);
        nodeMapping = new NodeMapping(this);

        cachedGroupedTransformerEnds = new HashMap<>();
        cachedGroupedRatioTapChangers = new HashMap<>();
        cachedGroupedRatioTapChangerTablePoints = new HashMap<>();
        cachedGroupedPhaseTapChangers = new HashMap<>();
        cachedGroupedPhaseTapChangerTablePoints = new HashMap<>();
        cachedGroupedShuntCompensatorPoints = new HashMap<>();
        cachedGroupedReactiveCapabilityCurveData = new HashMap<>();

        buildCaches();
    }

    public CgmesModel cgmes() {
        return cgmes;
    }

    public Network network() {
        return network;
    }

    public Config config() {
        return config;
    }

    public boolean nodeBreaker() {
        return nodeBreaker;
    }

    public NamingStrategy namingStrategy() {
        return namingStrategy;
    }

    public TerminalMapping terminalMapping() {
        return terminalMapping;
    }

    public void convertedTerminal(String terminalId, Terminal t, int n, PowerFlow f) {
        // Record the mapping between CGMES and IIDM terminals
        terminalMapping().add(terminalId, t, n);
        // Update the power flow at terminal. Check that IIDM allows setting it
        if (f.defined() && setPQAllowed(t)) {
            t.setP(f.p());
            t.setQ(f.q());
        }
    }

    private boolean setPQAllowed(Terminal t) {
        return t.getConnectable().getType() != IdentifiableType.BUSBAR_SECTION;
    }

    public NodeMapping nodeMapping() {
        return nodeMapping;
    }

    public NodeContainerMapping nodeContainerMapping() {
        return nodeContainerMapping;
    }

    public CgmesBoundary boundary() {
        return cgmesBoundary;
    }

    public DcMapping dc() {
        return dcMapping;
    }

    public LoadingLimitsMapping loadingLimitsMapping() {
        return loadingLimitsMapping;
    }

    public RegulatingControlMapping regulatingControlMapping() {
        return regulatingControlMapping;
    }

    public static String boundaryVoltageLevelId(String nodeId) {
        Objects.requireNonNull(nodeId);
        return nodeId + "_VL";
    }

    public static String boundarySubstationId(String nodeId) {
        Objects.requireNonNull(nodeId);
        return nodeId + "_S";
    }

    private void buildCaches() {
        buildCache(cachedGroupedTransformerEnds, cgmes().transformerEnds(), CgmesNames.POWER_TRANSFORMER);
        buildCache(cachedGroupedRatioTapChangers, cgmes().ratioTapChangers(), CgmesNames.POWER_TRANSFORMER);
        buildCache(cachedGroupedRatioTapChangerTablePoints, cgmes().ratioTapChangerTablePoints(), CgmesNames.RATIO_TAP_CHANGER_TABLE);
        buildCache(cachedGroupedPhaseTapChangers, cgmes().phaseTapChangers(), CgmesNames.POWER_TRANSFORMER);
        buildCache(cachedGroupedPhaseTapChangerTablePoints, cgmes().phaseTapChangerTablePoints(), CgmesNames.PHASE_TAP_CHANGER_TABLE);
        buildCache(cachedGroupedShuntCompensatorPoints, cgmes().nonlinearShuntCompensatorPoints(), "Shunt");
        buildCache(cachedGroupedReactiveCapabilityCurveData, cgmes().reactiveCapabilityCurveData(), "ReactiveCapabilityCurve");
    }

    private void buildCache(Map<String, PropertyBags> cache, PropertyBags ps, String groupName) {
        ps.forEach(p -> {
            String groupId = p.getId(groupName);
            cache.computeIfAbsent(groupId, b -> new PropertyBags()).add(p);
        });
    }

    public PropertyBags transformerEnds(String transformerId) {
        return cachedGroupedTransformerEnds.getOrDefault(transformerId, new PropertyBags());
    }

    public PropertyBags ratioTapChangers(String transformerId) {
        return cachedGroupedRatioTapChangers.getOrDefault(transformerId, new PropertyBags());
    }

    public PropertyBags ratioTapChangerTablePoints(String tableId) {
        return cachedGroupedRatioTapChangerTablePoints.getOrDefault(tableId, new PropertyBags());
    }

    public PropertyBags phaseTapChangers(String transformerId) {
        return cachedGroupedPhaseTapChangers.getOrDefault(transformerId, new PropertyBags());
    }

    public PropertyBags phaseTapChangerTablePoints(String tableId) {
        return cachedGroupedPhaseTapChangerTablePoints.getOrDefault(tableId, new PropertyBags());
    }

    public PropertyBags nonlinearShuntCompensatorPoints(String shuntId) {
        return cachedGroupedShuntCompensatorPoints.getOrDefault(shuntId, new PropertyBags());
    }

    public PropertyBags reactiveCapabilityCurveData(String curveId) {
        return cachedGroupedReactiveCapabilityCurveData.getOrDefault(curveId, new PropertyBags());
    }

    // Handling issues found during conversion

    public ReportNode getReportNode() {
        return network.getReportNodeContext().getReportNode();
    }

    public void pushReportNode(ReportNode node) {
        network.getReportNodeContext().pushReportNode(node);
    }

    public ReportNode popReportNode() {
        return network.getReportNodeContext().popReportNode();
    }

    private enum ConversionIssueCategory {
        INVALID("Invalid"),
        IGNORED("Ignored"),
        MISSING("Missing"),
        FIXED("Fixed"),
        PENDING("Pending");

        ConversionIssueCategory(String description) {
            this.description = description;
        }

        @Override
        public String toString() {
            return description;
        }

        private final String description;
    }

    public void invalid(String what, String reason) {
        handleIssue(ConversionIssueCategory.INVALID, what, reason);
    }

    public void invalid(String what, Supplier<String> reason) {
        handleIssue(ConversionIssueCategory.INVALID, what, reason);
    }

    public void ignored(String what, String reason) {
        handleIssue(ConversionIssueCategory.IGNORED, what, reason);
    }

    public void ignored(String what, Supplier<String> reason) {
        handleIssue(ConversionIssueCategory.IGNORED, what, reason);
    }

    public void pending(String what, Supplier<String> reason) {
        handleIssue(ConversionIssueCategory.PENDING, what, reason);
    }

    public void fixed(String what, String reason) {
        handleIssue(ConversionIssueCategory.FIXED, what, reason);
    }

    public void fixed(String what, Supplier<String> reason) {
        handleIssue(ConversionIssueCategory.FIXED, what, reason);
    }

    public void fixed(String what, String reason, double wrong, double fixed) {
        Supplier<String> reason1 = () -> String.format("%s. Wrong %.4f, was fixed to %.4f", reason, wrong, fixed);
        handleIssue(ConversionIssueCategory.FIXED, what, reason1);
    }

    public void missing(String what) {
        String reason1 = "";
        handleIssue(ConversionIssueCategory.MISSING, what, reason1);
    }

    public void missing(String what, Supplier<String> reason) {
        handleIssue(ConversionIssueCategory.MISSING, what, reason);
    }

    public void missing(String what, double defaultValue) {
        Supplier<String> reason1 = () -> String.format("Using default value %.4f", defaultValue);
        handleIssue(ConversionIssueCategory.MISSING, what, reason1);
    }

    private void handleIssue(ConversionIssueCategory category, String what, String reason) {
        handleIssue(category, what, () -> reason);
    }

    private void handleIssue(ConversionIssueCategory category, String what, Supplier<String> reason) {
        logIssue(category, what, reason);
    }

    private static void logIssue(ConversionIssueCategory category, String what, Supplier<String> reason) {
        if (LOG.isWarnEnabled()) {
            LOG.warn("{}: {}. Reason: {}", category, what, reason.get());
        }
    }

    private final CgmesModel cgmes;
    private final Network network;
    private final Config config;

    private final boolean nodeBreaker;
    private final NamingStrategy namingStrategy;
    private final NodeContainerMapping nodeContainerMapping;
    private final CgmesBoundary cgmesBoundary;
    private final TerminalMapping terminalMapping;
    private final NodeMapping nodeMapping;
    private final DcMapping dcMapping;
    private final LoadingLimitsMapping loadingLimitsMapping;
    private final RegulatingControlMapping regulatingControlMapping;

    private final Map<String, PropertyBags> cachedGroupedTransformerEnds;
    private final Map<String, PropertyBags> cachedGroupedRatioTapChangers;
    private final Map<String, PropertyBags> cachedGroupedRatioTapChangerTablePoints;
    private final Map<String, PropertyBags> cachedGroupedPhaseTapChangers;
    private final Map<String, PropertyBags> cachedGroupedPhaseTapChangerTablePoints;
    private final Map<String, PropertyBags> cachedGroupedShuntCompensatorPoints;
    private final Map<String, PropertyBags> cachedGroupedReactiveCapabilityCurveData;

    private static final Logger LOG = LoggerFactory.getLogger(Context.class);
}