NcContingencyCreator.java

/*
 * Copyright (c) 2023, 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/.
 */

package com.powsybl.openrao.data.crac.io.nc.craccreator.contingency;

import com.powsybl.contingency.ContingencyElementFactory;
import com.powsybl.contingency.ContingencyElementType;
import com.powsybl.iidm.network.Identifiable;
import com.powsybl.openrao.data.crac.api.ContingencyAdder;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.io.commons.api.ElementaryCreationContext;
import com.powsybl.openrao.data.crac.io.commons.api.ImportStatus;
import com.powsybl.openrao.data.crac.io.commons.api.StandardElementaryCreationContext;
import com.powsybl.openrao.data.crac.io.nc.NcCrac;
import com.powsybl.openrao.data.crac.io.nc.craccreator.NcAggregator;
import com.powsybl.openrao.data.crac.io.nc.craccreator.NcCracCreationContext;
import com.powsybl.iidm.network.Network;
import com.powsybl.openrao.data.crac.io.nc.objects.Contingency;
import com.powsybl.openrao.data.crac.io.nc.objects.ContingencyEquipment;
import com.powsybl.openrao.data.crac.io.commons.OpenRaoImportException;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Jean-Pierre Arnould {@literal <jean-pierre.arnould at rte-france.com>}
 */
public class NcContingencyCreator {

    private final Crac crac;

    private final Network network;

    private final Set<Contingency> nativeContingencies;

    private final Map<String, Set<ContingencyEquipment>> nativeContingencyEquipmentsPerNativeContingency;

    private Set<ElementaryCreationContext> ncProfileContingencyCreationContexts;
    private final NcCracCreationContext cracCreationContext;

    public NcContingencyCreator(Crac crac, Network network, NcCrac nativeCrac, NcCracCreationContext cracCreationContext) {
        this.crac = crac;
        this.network = network;
        this.nativeContingencies = nativeCrac.getContingencies();
        this.nativeContingencyEquipmentsPerNativeContingency = new NcAggregator<>(ContingencyEquipment::contingency).aggregate(nativeCrac.getContingencyEquipments());
        this.cracCreationContext = cracCreationContext;
        this.createAndAddContingencies();
    }

    private void createAndAddContingencies() {
        ncProfileContingencyCreationContexts = new HashSet<>();

        for (Contingency nativeContingency : nativeContingencies) {
            Set<ContingencyEquipment> nativeContingencyEquipments = nativeContingencyEquipmentsPerNativeContingency.getOrDefault(nativeContingency.mrid(), Set.of());
            try {
                addContingency(nativeContingency, nativeContingencyEquipments);
            } catch (OpenRaoImportException exception) {
                ncProfileContingencyCreationContexts.add(StandardElementaryCreationContext.notImported(nativeContingency.mrid(), null, exception.getImportStatus(), exception.getMessage()));
            }
        }

        cracCreationContext.setContingencyCreationContexts(ncProfileContingencyCreationContexts);
    }

    private void addContingency(Contingency nativeContingency, Set<ContingencyEquipment> nativeContingencyEquipments) {
        List<String> alterations = new ArrayList<>();

        if (!nativeContingency.normalMustStudy()) {
            throw new OpenRaoImportException(ImportStatus.NOT_FOR_RAO, formatNotImportedMessage(nativeContingency.mrid(), "its field mustStudy is set to false"));
        }

        ContingencyAdder contingencyAdder = crac.newContingency().withId(nativeContingency.mrid()).withName(nativeContingency.getUniqueName());
        addContingencyEquipments(nativeContingency.mrid(), nativeContingencyEquipments, contingencyAdder, alterations);
        contingencyAdder.add();

        ncProfileContingencyCreationContexts.add(StandardElementaryCreationContext.imported(nativeContingency.mrid(), null, nativeContingency.mrid(), !alterations.isEmpty(), String.join(" ", alterations)));
    }

    private void addContingencyEquipments(String contingencyId, Set<ContingencyEquipment> nativeContingencyEquipments, ContingencyAdder contingencyAdder, List<String> alterations) {
        if (nativeContingencyEquipments == null || nativeContingencyEquipments.isEmpty()) {
            throw new OpenRaoImportException(ImportStatus.INCOMPLETE_DATA, formatNotImportedMessage(contingencyId, "no contingency equipment is linked to that contingency"));
        }

        List<String> incorrectContingentStatusElements = new ArrayList<>();
        List<String> missingNetworkElements = new ArrayList<>();

        for (ContingencyEquipment nativeContingencyEquipment : nativeContingencyEquipments) {

            if (!nativeContingencyEquipment.isEquipmentOutOfService()) {
                incorrectContingentStatusElements.add(nativeContingencyEquipment.equipment());
            } else {
                Identifiable<?> networkElement = nativeContingencyEquipment.getEquipmentInNetwork(network);
                if (networkElement == null) {
                    missingNetworkElements.add(nativeContingencyEquipment.equipment());
                } else {
                    ContingencyElementType contingencyElementType = ContingencyElementFactory.create(networkElement).getType();
                    contingencyAdder.withContingencyElement(networkElement.getId(), contingencyElementType);
                }
            }
        }

        incorrectContingentStatusElements.sort(String::compareTo);
        missingNetworkElements.sort(String::compareTo);

        if (incorrectContingentStatusElements.size() == nativeContingencyEquipments.size()) {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, formatNotImportedMessage(contingencyId, "all contingency equipments have an incorrect contingent status: %s".formatted(String.join(", ", incorrectContingentStatusElements))));
        }

        if (missingNetworkElements.size() == nativeContingencyEquipments.size()) {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, formatNotImportedMessage(contingencyId, "all contingency equipments are missing in the network: %s".formatted(String.join(", ", missingNetworkElements))));
        }

        if (!incorrectContingentStatusElements.isEmpty()) {
            alterations.add("Incorrect contingent status for equipment(s): %s.".formatted(String.join(", ", incorrectContingentStatusElements)));
        }

        if (!missingNetworkElements.isEmpty()) {
            alterations.add("Missing contingent equipment(s) in network: %s.".formatted(String.join(", ", missingNetworkElements)));
        }
    }

    private static String formatNotImportedMessage(String contingencyId, String reason) {
        return "Contingency %s will not be imported because %s".formatted(contingencyId, reason);
    }
}