PstRangeActionCreator.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.openrao.commons.OpenRaoException;
import com.powsybl.contingency.Contingency;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.cnec.Cnec;
import com.powsybl.openrao.data.crac.api.range.RangeType;
import com.powsybl.openrao.data.crac.api.rangeaction.PstRangeActionAdder;
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.api.parameters.RangeActionGroup;
import com.powsybl.openrao.data.crac.io.commons.OpenRaoImportException;
import com.powsybl.openrao.data.crac.io.cim.parameters.CimCracCreationParameters;
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.iidm.IidmPstHelper;
import com.powsybl.iidm.network.Country;
import com.powsybl.iidm.network.Network;

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

/**
 * @author Godelaine de Montmorillon {@literal <godelaine.demontmorillon at rte-france.com>}
 */
public class PstRangeActionCreator {
    private final String createdRemedialActionId;
    private final String createdRemedialActionName;
    private final String applicationModeMarketObjectStatus;
    private RemedialActionSeriesCreationContext pstRangeActionCreationContext;
    private final RemedialActionRegisteredResource pstRegisteredResource;
    private final List<Contingency> contingencies;
    private final List<String> invalidContingencies;
    private final Set<Cnec<?>> cnecs;
    private PstRangeActionAdder pstRangeActionAdder;
    private final Country sharedDomain;

    public PstRangeActionCreator(RemedialActionSeries remedialActionSeries, List<Contingency> contingencies, List<String> invalidContingencies, Set<Cnec<?>> cnecs, Country sharedDomain) {
        this.createdRemedialActionId = remedialActionSeries.getMRID();
        this.createdRemedialActionName = remedialActionSeries.getName();
        this.applicationModeMarketObjectStatus = remedialActionSeries.getApplicationModeMarketObjectStatusStatus();
        this.pstRegisteredResource = remedialActionSeries.getRegisteredResource().get(0);
        this.contingencies = contingencies;
        this.invalidContingencies = invalidContingencies;
        this.cnecs = cnecs;
        this.sharedDomain = sharedDomain;
    }

    public void createPstRangeActionAdder(Crac crac, Network network, CimCracCreationParameters cimCracCreationParameters) {
        // --- Market Object status: define RangeType
        String marketObjectStatusStatus = pstRegisteredResource.getMarketObjectStatusStatus();
        if (Objects.isNull(marketObjectStatusStatus)) {
            this.pstRangeActionCreationContext = PstRangeActionSeriesCreationContext.notImported(createdRemedialActionId, ImportStatus.INCOMPLETE_DATA, "Missing marketObjectStatus");
            return;
        }
        try {
            RangeType rangeType = defineRangeType(marketObjectStatusStatus);

            // Add remedial action
            // --- For now, do not set operator.
            // --- For now, do not set group id.
            String networkElementId = pstRegisteredResource.getMRID().getValue();

            IidmPstHelper pstHelper = new IidmPstHelper(networkElementId, network);
            if (!pstHelper.isValid()) {
                this.pstRangeActionCreationContext = PstRangeActionSeriesCreationContext.notImported(createdRemedialActionId, ImportStatus.ELEMENT_NOT_FOUND_IN_NETWORK, String.format("%s", pstHelper.getInvalidReason()));
                return;
            }

            this.pstRangeActionAdder = crac.newPstRangeAction()
                .withId(createdRemedialActionId)
                .withName(createdRemedialActionName)
                .withOperator(CimConstants.readOperator(createdRemedialActionId))
                .withNetworkElement(pstHelper.getIdInNetwork())
                .withInitialTap(pstHelper.getInitialTap())
                .withTapToAngleConversionMap(pstHelper.getTapToAngleConversionMap());

            // ---- add groupId if present
            if (cimCracCreationParameters != null && cimCracCreationParameters.getRangeActionGroups() != null) {
                List<String> raGroups = cimCracCreationParameters.getRangeActionGroups().stream()
                    .filter(rangeActionGroup -> rangeActionGroup.getRangeActionsIds().contains(createdRemedialActionId))
                    .map(RangeActionGroup::toString)
                    .toList();
                if (raGroups.size() > 1) {
                    this.pstRangeActionCreationContext = RemedialActionSeriesCreationContext.notImported(createdRemedialActionId, ImportStatus.INCONSISTENCY_IN_DATA, String.format("Multiple (%s) groups defined for range action %s", raGroups.size(), createdRemedialActionId));
                    return;
                }
                if (!raGroups.isEmpty()) {
                    pstRangeActionAdder.withGroupId(raGroups.get(0));
                }
            }

            // -- add speed if present
            if (cimCracCreationParameters != null && cimCracCreationParameters.getRangeActionSpeedSet() != null) {
                cimCracCreationParameters.getRangeActionSpeedSet().stream()
                    .filter(rangeActionSpeed -> rangeActionSpeed.getRangeActionId().equals(createdRemedialActionId))
                    .forEach(rangeActionSpeed -> pstRangeActionAdder.withSpeed(rangeActionSpeed.getSpeed()));
            }

            // --- Resource capacity
            defineTapRange(pstRangeActionAdder, pstHelper, rangeType);
            RemedialActionSeriesCreator.addUsageRules(crac, applicationModeMarketObjectStatus, pstRangeActionAdder, contingencies, invalidContingencies, cnecs, sharedDomain);

            this.pstRangeActionCreationContext = RemedialActionSeriesCreator.importPstRaWithContingencies(createdRemedialActionId, pstRegisteredResource.getMRID().getValue(), pstRegisteredResource.getName(), invalidContingencies);
        } catch (OpenRaoImportException e) {
            this.pstRangeActionCreationContext = RemedialActionSeriesCreationContext.notImported(createdRemedialActionId, e.getImportStatus(), e.getMessage());
        }
    }

    private RangeType defineRangeType(String marketObjectStatusStatus) {
        if (marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.ABSOLUTE.getStatus())) {
            return RangeType.ABSOLUTE;
        } else if (marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.RELATIVE_TO_INITIAL_NETWORK.getStatus())) {
            return RangeType.RELATIVE_TO_INITIAL_NETWORK;
        } else if (marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.RELATIVE_TO_PREVIOUS_INSTANT1.getStatus()) || marketObjectStatusStatus.equals(CimConstants.MarketObjectStatus.RELATIVE_TO_PREVIOUS_INSTANT2.getStatus())) {
            return RangeType.RELATIVE_TO_PREVIOUS_INSTANT;
        } 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)", marketObjectStatusStatus));
        } else {
            throw new OpenRaoImportException(ImportStatus.INCONSISTENCY_IN_DATA, String.format("Wrong marketObjectStatusStatus: %s", marketObjectStatusStatus));
        }
    }

    private void defineTapRange(PstRangeActionAdder pstRangeActionAdder, IidmPstHelper pstHelper, RangeType rangeType) {
        String unitSymbol = pstRegisteredResource.getResourceCapacityUnitSymbol();
        BigDecimal resourceCapacityMinimumCapacity = pstRegisteredResource.getResourceCapacityMinimumCapacity();
        BigDecimal resourceCapacityMaximumCapacity = pstRegisteredResource.getResourceCapacityMaximumCapacity();
        // Min and Max defined
        if (Objects.nonNull(resourceCapacityMinimumCapacity) && Objects.nonNull(resourceCapacityMaximumCapacity)) {
            int minCapacity = resourceCapacityMinimumCapacity.intValue();
            int maxCapacity = resourceCapacityMaximumCapacity.intValue();
            RemedialActionSeriesCreator.checkPstUnit(unitSymbol);
            addTapRangeWithMinAndMaxTap(pstRangeActionAdder, pstHelper, minCapacity, maxCapacity, rangeType);
        } else if (Objects.nonNull(resourceCapacityMaximumCapacity)) {
            int maxCapacity = resourceCapacityMaximumCapacity.intValue();
            RemedialActionSeriesCreator.checkPstUnit(unitSymbol);
            addTapRangeWithMaxTap(pstRangeActionAdder, pstHelper, maxCapacity, rangeType);
        } else if (Objects.nonNull(resourceCapacityMinimumCapacity)) {
            int minCapacity = resourceCapacityMinimumCapacity.intValue();
            RemedialActionSeriesCreator.checkPstUnit(unitSymbol);
            addTapRangeWithMinTap(pstRangeActionAdder, pstHelper, minCapacity, rangeType);
        }
    }

    private void addTapRangeWithMinTap(PstRangeActionAdder pstRangeActionAdder, IidmPstHelper pstHelper, int minCapacity, RangeType rangeType) {
        int minTap = minCapacity;
        if (rangeType.equals(RangeType.ABSOLUTE)) {
            minTap = pstHelper.normalizeTap(minTap, PstHelper.TapConvention.STARTS_AT_ONE);
        }
        pstRangeActionAdder.newTapRange()
            .withMinTap(minTap)
            .withRangeType(rangeType)
            .add();
    }

    private void addTapRangeWithMaxTap(PstRangeActionAdder pstRangeActionAdder, IidmPstHelper pstHelper, int maxCapacity, RangeType rangeType) {
        int maxTap = maxCapacity;
        if (rangeType.equals(RangeType.ABSOLUTE)) {
            maxTap = pstHelper.normalizeTap(maxTap, PstHelper.TapConvention.STARTS_AT_ONE);
        }
        pstRangeActionAdder.newTapRange()
            .withMaxTap(maxTap)
            .withRangeType(rangeType)
            .add();
    }

    private void addTapRangeWithMinAndMaxTap(PstRangeActionAdder pstRangeActionAdder, IidmPstHelper pstHelper, int minCapacity, int maxCapacity, RangeType rangeType) {
        int minTap = minCapacity;
        int maxTap = maxCapacity;
        if (rangeType.equals(RangeType.ABSOLUTE)) {
            minTap = pstHelper.normalizeTap(minTap, PstHelper.TapConvention.STARTS_AT_ONE);
            maxTap = pstHelper.normalizeTap(maxTap, PstHelper.TapConvention.STARTS_AT_ONE);
        }
        pstRangeActionAdder.newTapRange()
            .withMinTap(minTap)
            .withMaxTap(maxTap)
            .withRangeType(rangeType)
            .add();
    }

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

    public RemedialActionSeriesCreationContext getPstRangeActionCreationContext() {
        return pstRangeActionCreationContext;
    }

    public PstRangeActionAdder getPstRangeActionAdder() {
        return pstRangeActionAdder;
    }
}