NetworkActionCreator.java

/*
 * Copyright (c) 2022, 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.cim.craccreator;

import com.powsybl.contingency.Contingency;
import com.powsybl.iidm.network.*;
import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.cnec.Cnec;
import com.powsybl.openrao.data.crac.api.networkaction.ActionType;
import com.powsybl.openrao.data.crac.api.networkaction.NetworkActionAdder;
import com.powsybl.openrao.data.crac.io.cim.xsd.RemedialActionSeries;
import com.powsybl.openrao.data.crac.io.commons.api.ImportStatus;
import com.powsybl.openrao.data.crac.io.commons.OpenRaoImportException;
import com.powsybl.openrao.data.crac.io.cim.xsd.RemedialActionRegisteredResource;
import com.powsybl.openrao.data.crac.io.commons.PstHelper;
import com.powsybl.openrao.data.crac.io.commons.cgmes.CgmesBranchHelper;
import com.powsybl.openrao.data.crac.io.commons.iidm.IidmPstHelper;

import java.util.List;
import java.util.Objects;
import java.util.Set;

import static com.powsybl.openrao.data.crac.io.cim.craccreator.CimConstants.MEGAWATT_UNIT_SYMBOL;

/**
 * @author Godelaine de Montmorillon {@literal <godelaine.demontmorillon at rte-france.com>}
 */
public class NetworkActionCreator {
    private final Crac crac;
    private final Network network;
    private final String createdRemedialActionId;
    private final String createdRemedialActionName;
    private final String applicationModeMarketObjectStatus;
    private final List<RemedialActionRegisteredResource> networkActionRegisteredResources;
    private RemedialActionSeriesCreationContext networkActionCreationContext;
    private NetworkActionAdder networkActionAdder;
    private final List<Contingency> contingencies;
    private final List<String> invalidContingencies;
    private final Set<Cnec<?>> cnecs;
    private final Country sharedDomain;

    public NetworkActionCreator(Crac crac, Network network, RemedialActionSeries remedialActionSeries, List<Contingency> contingencies, List<String> invalidContingencies, Set<Cnec<?>> cnecs, Country sharedDomain) {
        this.crac = crac;
        this.network = network;
        this.createdRemedialActionId = remedialActionSeries.getMRID();
        this.createdRemedialActionName = remedialActionSeries.getName();
        this.applicationModeMarketObjectStatus = remedialActionSeries.getApplicationModeMarketObjectStatusStatus();
        this.networkActionRegisteredResources = remedialActionSeries.getRegisteredResource();
        this.contingencies = contingencies;
        this.invalidContingencies = invalidContingencies;
        this.cnecs = cnecs;
        this.sharedDomain = sharedDomain;
    }

    public void createNetworkActionAdder() {
        this.networkActionAdder = crac.newNetworkAction()
            .withId(createdRemedialActionId)
            .withName(createdRemedialActionName)
            .withOperator(CimConstants.readOperator(createdRemedialActionId));

        try {
            RemedialActionSeriesCreator.addUsageRules(crac, applicationModeMarketObjectStatus, networkActionAdder, contingencies, invalidContingencies, cnecs, sharedDomain);

            // Elementary actions
            for (RemedialActionRegisteredResource remedialActionRegisteredResource : networkActionRegisteredResources) {
                String psrType = remedialActionRegisteredResource.getPSRTypePsrType();
                String elementaryActionId = remedialActionRegisteredResource.getName();
                if (Objects.isNull(psrType)) {
                    this.networkActionCreationContext = RemedialActionSeriesCreationContext.notImported(createdRemedialActionId, ImportStatus.INCOMPLETE_DATA, String.format("Missing psrType on elementary action %s", elementaryActionId));
                    return;
                }
                if (psrType.equals(CimConstants.PsrType.PST.getStatus())) {
                    // PST setpoint elementary action
                    addPstSetpointElementaryAction(elementaryActionId, remedialActionRegisteredResource, networkActionAdder);
                } else if (psrType.equals(CimConstants.PsrType.GENERATION.getStatus())) {
                    addGeneratorElementaryAction(elementaryActionId, remedialActionRegisteredResource, networkActionAdder);
                } else if (psrType.equals(CimConstants.PsrType.LOAD.getStatus())) {
                    addLoadElementaryAction(elementaryActionId, remedialActionRegisteredResource, networkActionAdder);
                } else if (psrType.equals(CimConstants.PsrType.LINE.getStatus()) && remedialActionRegisteredResource.getMarketObjectStatusStatus().equals(CimConstants.MarketObjectStatus.ABSOLUTE.getStatus())) {
                    this.networkActionCreationContext = RemedialActionSeriesCreationContext.notImported(createdRemedialActionId, ImportStatus.NOT_YET_HANDLED_BY_OPEN_RAO, String.format("Modify line impedance as remedial action on elementary action %s", elementaryActionId));
                    return;
                } else if (psrType.equals(CimConstants.PsrType.TIE_LINE.getStatus()) || psrType.equals(CimConstants.PsrType.LINE.getStatus()) || psrType.equals(CimConstants.PsrType.TRANSFORMER.getStatus())) {
                    addTerminalsConnectionElementaryAction(elementaryActionId, remedialActionRegisteredResource, networkActionAdder);
                } else if (psrType.equals(CimConstants.PsrType.CIRCUIT_BREAKER.getStatus())) {
                    addSwitchElementaryAction(elementaryActionId, remedialActionRegisteredResource, networkActionAdder);
                } else if (psrType.equals(CimConstants.PsrType.DEPRECATED_LINE.getStatus())) {
                    this.networkActionCreationContext = RemedialActionSeriesCreationContext.notImported(createdRemedialActionId, ImportStatus.INCONSISTENCY_IN_DATA, String.format("Wrong psrType: %s, deprecated LINE psrType on elementary action %s", psrType, elementaryActionId));
                    return;
                } else {
                    this.networkActionCreationContext = RemedialActionSeriesCreationContext.notImported(createdRemedialActionId, ImportStatus.INCONSISTENCY_IN_DATA, String.format("Wrong psrType: %s on elementary action %s", psrType, elementaryActionId));
                    return;
                }
            }
            this.networkActionCreationContext = RemedialActionSeriesCreator.importWithContingencies(createdRemedialActionId, invalidContingencies);
        } catch (OpenRaoImportException e) {
            this.networkActionCreationContext = RemedialActionSeriesCreationContext.notImported(createdRemedialActionId, e.getImportStatus(), e.getMessage());
        }
    }

    private void addPstSetpointElementaryAction(String elementaryActionId, RemedialActionRegisteredResource remedialActionRegisteredResource, NetworkActionAdder networkActionAdder) {
        RemedialActionSeriesCreator.checkPstUnit(remedialActionRegisteredResource.getResourceCapacityUnitSymbol());

        // Pst helper
        String networkElementId = remedialActionRegisteredResource.getMRID().getValue();
        IidmPstHelper pstHelper = new IidmPstHelper(networkElementId, network);
        if (!pstHelper.isValid()) {
            throw new OpenRaoImportException(ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK, String.format("%s on elementary action %s", pstHelper.getInvalidReason(), elementaryActionId));
        }
        // --- Market Object status: define RangeType
        String marketObjectStatusStatus = remedialActionRegisteredResource.getMarketObjectStatusStatus();
        int setpoint;
        if (Objects.isNull(marketObjectStatusStatus)) {
            throw new OpenRaoImportException(ImportStatus.INCOMPLETE_DATA, String.format("Missing marketObjectStatus on elementary action %s", elementaryActionId));
        } else if (marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.ABSOLUTE.getStatus())) {
            setpoint = pstHelper.normalizeTap(remedialActionRegisteredResource.getResourceCapacityDefaultCapacity().intValue(), PstHelper.TapConvention.STARTS_AT_ONE);
        } else if (marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.RELATIVE_TO_INITIAL_NETWORK.getStatus())) {
            setpoint = pstHelper.getInitialTap() + remedialActionRegisteredResource.getResourceCapacityDefaultCapacity().intValue();
        } else if (marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.OPEN.getStatus()) || marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.CLOSE.getStatus())) {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("Wrong marketObjectStatusStatus: %s, PST can no longer be opened/closed (deprecated) on elementary action %s", marketObjectStatusStatus, elementaryActionId));
        } else {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("Wrong marketObjectStatusStatus: %s on elementary action %s", marketObjectStatusStatus, elementaryActionId));
        }
        networkActionAdder.newPhaseTapChangerTapPositionAction()
            .withNetworkElement(networkElementId)
            .withTapPosition((pstHelper.getLowTapPosition() + pstHelper.getHighTapPosition()) / 2 + setpoint)
            .add();
    }

    private int checkAndComputeSetpointForInjectionElementaryAction(String elementaryActionId, RemedialActionRegisteredResource remedialActionRegisteredResource) {
        // Injection range actions aren't handled
        if (Objects.nonNull(remedialActionRegisteredResource.getResourceCapacityMinimumCapacity()) || Objects.nonNull(remedialActionRegisteredResource.getResourceCapacityMaximumCapacity())) {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("Injection setpoint elementary action %s should not have a min or max capacity defined", elementaryActionId));
        }
        // Market object status
        String marketObjectStatusStatus = remedialActionRegisteredResource.getMarketObjectStatusStatus();
        if (Objects.isNull(marketObjectStatusStatus)) {
            throw new OpenRaoImportException(ImportStatus.INCOMPLETE_DATA, String.format("Missing marketObjectStatus on injection setpoint elementary action %s", elementaryActionId));
        }
        if (marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.ABSOLUTE.getStatus())) {
            if (Objects.isNull(remedialActionRegisteredResource.getResourceCapacityDefaultCapacity())
                || Objects.isNull(remedialActionRegisteredResource.getResourceCapacityUnitSymbol())) {
                throw new OpenRaoImportException(ImportStatus.INCOMPLETE_DATA, String.format("Injection setpoint elementary action %s with ABSOLUTE marketObjectStatus should have a defaultCapacity and a unitSymbol", elementaryActionId));
            }
            if (!remedialActionRegisteredResource.getResourceCapacityUnitSymbol().equals(MEGAWATT_UNIT_SYMBOL)) {
                throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("Wrong unit symbol for injection setpoint elementary action %s with ABSOLUTE marketObjectStatus : %s", elementaryActionId, remedialActionRegisteredResource.getResourceCapacityUnitSymbol()));
            }
            return remedialActionRegisteredResource.getResourceCapacityDefaultCapacity().intValue();
        } else if (marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.STOP.getStatus())) {
            if (Objects.nonNull(remedialActionRegisteredResource.getResourceCapacityDefaultCapacity())
                || Objects.nonNull(remedialActionRegisteredResource.getResourceCapacityUnitSymbol())) {
                throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("Injection setpoint elementary action %s with STOP marketObjectStatus shouldn't have a defaultCapacity nor a unitSymbol", elementaryActionId));
            }
            return 0;
        } else {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("Wrong marketObjectStatusStatus: %s on injection setpoint elementary action %s", marketObjectStatusStatus, elementaryActionId));
        }
    }

    private Identifiable<?> checkAndComputeNetworkElementForInjectionElementaryAction(RemedialActionRegisteredResource remedialActionRegisteredResource) {
        String networkElementId = remedialActionRegisteredResource.getMRID().getValue();
        Identifiable<?> networkElement = network.getIdentifiable(networkElementId);
        if (Objects.isNull(networkElement)) {
            throw new OpenRaoImportException(ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK, String.format("%s not found in network", networkElementId));
        }
        return networkElement;
    }

    private void addGeneratorElementaryAction(String elementaryActionId, RemedialActionRegisteredResource remedialActionRegisteredResource, NetworkActionAdder networkActionAdder) {
        int setpoint = checkAndComputeSetpointForInjectionElementaryAction(elementaryActionId, remedialActionRegisteredResource);
        Identifiable<?> networkElement = checkAndComputeNetworkElementForInjectionElementaryAction(remedialActionRegisteredResource);
        if (networkElement.getType() != IdentifiableType.GENERATOR) {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("%s is not a generator but a %s while psrType is %s", networkElement.getId(), networkElement.getType(), CimConstants.PsrType.GENERATION.getStatus()));
        }
        networkActionAdder.newGeneratorAction()
            .withNetworkElement(networkElement.getId())
            .withActivePowerValue(setpoint)
            .add();
    }

    private void addLoadElementaryAction(String elementaryActionId, RemedialActionRegisteredResource remedialActionRegisteredResource, NetworkActionAdder networkActionAdder) {
        int setpoint = checkAndComputeSetpointForInjectionElementaryAction(elementaryActionId, remedialActionRegisteredResource);
        Identifiable<?> networkElement = checkAndComputeNetworkElementForInjectionElementaryAction(remedialActionRegisteredResource);
        if (networkElement.getType() != IdentifiableType.LOAD) {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("%s is not a load but a %s while psrType is %s", networkElement.getId(), networkElement.getType(), CimConstants.PsrType.LOAD.getStatus()));
        }
        networkActionAdder.newLoadAction()
            .withNetworkElement(networkElement.getId())
            .withActivePowerValue(setpoint)
            .add();
    }

    private ActionType checkAndComputeActionTypeForTopologicalElementaryAction(String elementaryActionId, RemedialActionRegisteredResource remedialActionRegisteredResource) {
        if (Objects.nonNull(remedialActionRegisteredResource.getResourceCapacityMinimumCapacity()) || Objects.nonNull(remedialActionRegisteredResource.getResourceCapacityMaximumCapacity()) || Objects.nonNull(remedialActionRegisteredResource.getResourceCapacityDefaultCapacity())) {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("Topological elementary action %s should not have any resource capacity defined", elementaryActionId));
        }
        // Market object status
        String marketObjectStatusStatus = remedialActionRegisteredResource.getMarketObjectStatusStatus();
        if (Objects.isNull(marketObjectStatusStatus)) {
            throw new OpenRaoImportException(ImportStatus.INCOMPLETE_DATA, String.format("Missing marketObjectStatus on topological elementary action %s", elementaryActionId));
        } else if (marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.OPEN.getStatus())) {
            return ActionType.OPEN;
        } else if (marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.CLOSE.getStatus())) {
            return ActionType.CLOSE;
        } else {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("Wrong marketObjectStatusStatus: %s on topological elementary action %s", marketObjectStatusStatus, elementaryActionId));
        }
    }

    private void addTerminalsConnectionElementaryAction(String elementaryActionId, RemedialActionRegisteredResource remedialActionRegisteredResource, NetworkActionAdder networkActionAdder) {
        ActionType actionType = checkAndComputeActionTypeForTopologicalElementaryAction(elementaryActionId, remedialActionRegisteredResource);
        String networkElementId = remedialActionRegisteredResource.getMRID().getValue();
        Identifiable<?> element = network.getIdentifiable(networkElementId);
        if (Objects.isNull(element)) {
            // Check that network element is not half a tie line
            CgmesBranchHelper branchHelper = new CgmesBranchHelper(networkElementId, network);
            if (!branchHelper.isValid()) {
                throw new OpenRaoImportException(ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK, String.format("%s not in network on topological elementary action %s", networkElementId, elementaryActionId));
            }
            networkElementId = branchHelper.getIdInNetwork();
            element = branchHelper.getBranch();
        }
        if (element instanceof Branch) {
            networkActionAdder.newTerminalsConnectionAction()
                .withNetworkElement(networkElementId)
                .withActionType(actionType)
                .add();
        } else {
            throw new OpenRaoImportException(ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK, String.format("%s is not branch but a %s", networkElementId, element.getType()));
        }
    }

    private void addSwitchElementaryAction(String elementaryActionId, RemedialActionRegisteredResource remedialActionRegisteredResource, NetworkActionAdder networkActionAdder) {
        ActionType actionType = checkAndComputeActionTypeForTopologicalElementaryAction(elementaryActionId, remedialActionRegisteredResource);
        String networkElementId = remedialActionRegisteredResource.getMRID().getValue();
        Identifiable<?> element = network.getIdentifiable(networkElementId);
        if (Objects.isNull(element)) {
            throw new OpenRaoImportException(ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK, String.format("%s not found in network %s", networkElementId, network.getId()));
        }
        if (element.getType() == IdentifiableType.SWITCH) {
            networkActionAdder.newSwitchAction()
                .withNetworkElement(networkElementId)
                .withActionType(actionType)
                .add();
        } else {
            throw new OpenRaoImportException(ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK, String.format("%s is not a switch but a %s", networkElementId, element.getType()));
        }
    }

    public void addNetworkAction() {
        if (this.networkActionCreationContext.isImported() && this.networkActionAdder != null) {
            try {
                this.networkActionAdder.add();
            } catch (OpenRaoException e) {
                this.networkActionCreationContext = RemedialActionSeriesCreationContext.notImported(this.networkActionCreationContext.getNativeObjectId(), ImportStatus.INCONSISTENCY_IN_DATA, e.getMessage());
            }
        }
    }

    public RemedialActionSeriesCreationContext getNetworkActionCreationContext() {
        return networkActionCreationContext;
    }

    public NetworkActionAdder getNetworkActionAdder() {
        return networkActionAdder;
    }
}