SweRemedialActionSeriesCreator.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.raoresult.io.cne.swe;

import com.powsybl.contingency.Contingency;
import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.InstantKind;
import com.powsybl.openrao.data.crac.api.RemedialAction;
import com.powsybl.openrao.data.crac.api.State;
import com.powsybl.openrao.data.crac.api.networkaction.NetworkAction;
import com.powsybl.openrao.data.crac.api.rangeaction.HvdcRangeAction;
import com.powsybl.openrao.data.crac.api.rangeaction.PstRangeAction;
import com.powsybl.openrao.data.crac.io.cim.craccreator.CimCracCreationContext;
import com.powsybl.openrao.data.crac.io.cim.craccreator.PstRangeActionSeriesCreationContext;
import com.powsybl.openrao.data.crac.io.cim.craccreator.RemedialActionSeriesCreationContext;
import com.powsybl.openrao.data.raoresult.api.ComputationStatus;
import com.powsybl.openrao.data.raoresult.api.RaoResult;
import com.powsybl.openrao.data.raoresult.io.cne.swe.xsd.RemedialActionRegisteredResource;
import com.powsybl.openrao.data.raoresult.io.cne.swe.xsd.RemedialActionSeries;
import org.apache.commons.lang3.NotImplementedException;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

import static com.powsybl.openrao.data.raoresult.io.cne.commons.CneConstants.*;

/**
 * Generates RemedialActionSeries for SWE CNE format
 *
 * @author Philippe Edwards {@literal <philippe.edwards at rte-france.com>}
 */
public class SweRemedialActionSeriesCreator {
    private final SweCneHelper sweCneHelper;
    private final CimCracCreationContext cracCreationContext;

    public SweRemedialActionSeriesCreator(SweCneHelper sweCneHelper, CimCracCreationContext cracCreationContext) {
        this.sweCneHelper = sweCneHelper;
        this.cracCreationContext = cracCreationContext;
    }

    public List<RemedialActionSeries> generateRaSeries(Contingency contingency) {
        List<RemedialActionSeries> remedialActionSeriesList = new ArrayList<>();
        Crac crac = sweCneHelper.getCrac();
        List<RemedialActionSeriesCreationContext> sortedRas = cracCreationContext.getRemedialActionSeriesCreationContexts().stream()
            .filter(RemedialActionSeriesCreationContext::isImported)
            .sorted(Comparator.comparing(RemedialActionSeriesCreationContext::getNativeObjectId))
            .toList();
        if (contingency == null) {
            //PREVENTIVE
            sortedRas.forEach(
                raSeriesCreationContext -> {
                    RemedialActionSeries raSeries = generateRaSeries(crac.getPreventiveState(), raSeriesCreationContext, false);
                    if (Objects.nonNull(raSeries)) {
                        remedialActionSeriesList.add(raSeries);
                    }
                }
            );
        } else {
            //CURATIVE && AUTO
            sortedRas.forEach(
                raSeriesCreationContext -> {
                    RemedialActionSeries raSeries = generateRaSeries(crac.getState(contingency, crac.getInstant(InstantKind.AUTO)), raSeriesCreationContext, false);
                    if (Objects.nonNull(raSeries)) {
                        remedialActionSeriesList.add(raSeries);
                    }
                }
            );
            sortedRas.forEach(
                raSeriesCreationContext -> {
                    RemedialActionSeries raSeries = generateRaSeries(crac.getState(contingency, crac.getInstant(InstantKind.CURATIVE)), raSeriesCreationContext, false);
                    if (Objects.nonNull(raSeries)) {
                        remedialActionSeriesList.add(raSeries);
                    }
                }
            );
        }
        return remedialActionSeriesList;
    }

    public List<RemedialActionSeries> generateRaSeriesReference(Contingency contingency) {
        List<RemedialActionSeries> remedialActionSeriesList = new ArrayList<>();
        Crac crac = sweCneHelper.getCrac();
        List<RemedialActionSeriesCreationContext> sortedRas = cracCreationContext.getRemedialActionSeriesCreationContexts().stream()
            .filter(RemedialActionSeriesCreationContext::isImported)
            .sorted(Comparator.comparing(RemedialActionSeriesCreationContext::getNativeObjectId))
            .toList();
        if (contingency == null) {
            //PREVENTIVE
            sortedRas.forEach(
                raSeriesCreationContext -> {
                    RemedialActionSeries raSeries = generateRaSeries(crac.getPreventiveState(), raSeriesCreationContext, true);
                    if (Objects.nonNull(raSeries)) {
                        remedialActionSeriesList.add(raSeries);
                    }
                }
            );
        } else {
            //for the B57, in a contingency case, we want all remedial actions with an effect on the cnecs, so PREVENTIVE, CURATIVE && AUTO
            sortedRas.forEach(
                raSeriesCreationContext -> {
                    RemedialActionSeries raSeries = generateRaSeries(crac.getPreventiveState(), raSeriesCreationContext, true);
                    if (Objects.nonNull(raSeries)) {
                        remedialActionSeriesList.add(raSeries);
                    }
                }
            );
            sortedRas.forEach(
                raSeriesCreationContext -> {
                    RemedialActionSeries raSeries = generateRaSeries(crac.getState(contingency, crac.getInstant(InstantKind.AUTO)), raSeriesCreationContext, true);
                    if (Objects.nonNull(raSeries)) {
                        remedialActionSeriesList.add(raSeries);
                    }
                }
            );
            sortedRas.forEach(
                raSeriesCreationContext -> {
                    RemedialActionSeries raSeries = generateRaSeries(crac.getState(contingency, crac.getInstant(InstantKind.CURATIVE)), raSeriesCreationContext, true);
                    if (Objects.nonNull(raSeries)) {
                        remedialActionSeriesList.add(raSeries);
                    }
                }
            );
        }
        return remedialActionSeriesList;
    }

    private RemedialActionSeries generateRaSeries(State state, RemedialActionSeriesCreationContext context, boolean onlyReference) {
        RaoResult raoResult = sweCneHelper.getRaoResult();
        Crac crac = sweCneHelper.getCrac();
        List<RemedialAction<?>> usedRas = new ArrayList<>();
        if (raoResult == null) {
            return null;
        }
        context.getCreatedObjectsIds().stream().sorted()
                .map(crac::getRemedialAction)
                .filter(ra -> raoResult.isActivatedDuringState(state, ra)).forEach(usedRas::add);
        if (!raoResult.getComputationStatus().equals(ComputationStatus.FAILURE)) {
            context.getCreatedObjectsIds().stream().sorted()
                .map(crac::getRemedialAction).filter(ra ->
                    raoResult.getActivatedRangeActionsDuringState(state).stream().anyMatch(cra -> cra.getId().equals(ra.getId())) ||
                        raoResult.getActivatedNetworkActionsDuringState(state).stream().anyMatch(cra -> cra.getId().equals(ra.getId()))
                ).forEach(usedRas::add);
        }
        for (RemedialAction<?> usedRa : usedRas) {
            if (usedRa instanceof NetworkAction networkAction) {
                return generateNetworkRaSeries(networkAction, state);
            } else if (usedRa instanceof PstRangeAction pstRangeAction) {
                return generatePstRaSeries(pstRangeAction, state, context, onlyReference);
            } else if (usedRa instanceof HvdcRangeAction hvdcRangeAction) {
                // In case of an HVDC, the native getCrac has one series per direction, we select the one that corresponds to the sign of the setpoint
                if (context.isInverted() == (raoResult.getOptimizedSetPointOnState(state, hvdcRangeAction) < 0)) {
                    return generateHvdcRaSeries(hvdcRangeAction, state, context);
                }
            } else {
                throw new NotImplementedException(String.format("Range action of type %s not supported yet.", usedRa.getClass().getName()));
            }
        }
        return null;
    }

    private RemedialActionSeries generateNetworkRaSeries(NetworkAction networkAction, State state) {
        RemedialActionSeries remedialActionSeries = new RemedialActionSeries();
        remedialActionSeries.setMRID(networkAction.getId());
        remedialActionSeries.setName(networkAction.getName());
        remedialActionSeries.setApplicationModeMarketObjectStatusStatus(getApplicationModeMarketObjectStatusStatus(state));
        return remedialActionSeries;
    }

    private RemedialActionSeries generatePstRaSeries(PstRangeAction rangeAction, State state, RemedialActionSeriesCreationContext context, boolean onlyReference) {
        RemedialActionSeries remedialActionSeries = new RemedialActionSeries();
        remedialActionSeries.setMRID(rangeAction.getId() + "@" + sweCneHelper.getRaoResult().getOptimizedTapOnState(state, rangeAction) + "@");
        remedialActionSeries.setName(rangeAction.getName());
        remedialActionSeries.setApplicationModeMarketObjectStatusStatus(getApplicationModeMarketObjectStatusStatus(state));
        if (!onlyReference) {
            //we only want to write the registeredResource for B56s
            remedialActionSeries.getRegisteredResource().add(generateRegisteredResource(rangeAction, state, context));
        }
        return remedialActionSeries;
    }

    private RemedialActionSeries generateHvdcRaSeries(HvdcRangeAction rangeAction, State state, RemedialActionSeriesCreationContext context) {
        RemedialActionSeries remedialActionSeries = new RemedialActionSeries();
        double absoluteSetpoint = Math.abs(sweCneHelper.getRaoResult().getOptimizedSetPointOnState(state, rangeAction));
        String nativeId = context.getNativeObjectId();
        remedialActionSeries.setMRID(nativeId + "@" + absoluteSetpoint + "@");
        remedialActionSeries.setName(nativeId + "@" + absoluteSetpoint + "@");
        remedialActionSeries.setApplicationModeMarketObjectStatusStatus(getApplicationModeMarketObjectStatusStatus(state));
        return remedialActionSeries;
    }

    private String getApplicationModeMarketObjectStatusStatus(State state) {
        if (state.isPreventive()) {
            return PREVENTIVE_MARKET_OBJECT_STATUS;
        } else if (state.getInstant().isAuto()) {
            return AUTO_MARKET_OBJECT_STATUS;
        } else if (state.getInstant().isCurative()) {
            return CURATIVE_MARKET_OBJECT_STATUS;
        } else {
            throw new OpenRaoException(String.format("Unexpected instant for remedial action application : %s", state.getInstant().toString()));
        }
    }

    private RemedialActionRegisteredResource generateRegisteredResource(PstRangeAction pstRangeAction, State state, RemedialActionSeriesCreationContext context) {
        if (!(context instanceof PstRangeActionSeriesCreationContext pstContext)) {
            throw new OpenRaoException("Expected a PstRangeActionSeriesCreationContext");
        }

        RemedialActionRegisteredResource registeredResource = new RemedialActionRegisteredResource();
        registeredResource.setMRID(SweCneUtil.createResourceIDString(A02_CODING_SCHEME, pstContext.getNetworkElementNativeMrid()));
        registeredResource.setName(pstContext.getNetworkElementNativeName());
        registeredResource.setPSRTypePsrType(PST_RANGE_PSR_TYPE);
        registeredResource.setMarketObjectStatusStatus(ABSOLUTE_MARKET_OBJECT_STATUS);
        registeredResource.setResourceCapacityDefaultCapacity(new BigDecimal(sweCneHelper.getRaoResult().getOptimizedTapOnState(state, pstRangeAction)));
        registeredResource.setResourceCapacityUnitSymbol(WITHOUT_UNIT_SYMBOL);

        return registeredResource;
    }

}