FbConstraintCracCreator.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.fbconstraint;

import com.powsybl.iidm.network.Network;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.InstantKind;
import com.powsybl.iidm.network.TwoSides;
import com.powsybl.openrao.data.crac.api.parameters.CracCreationParameters;
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.fbconstraint.parameters.FbConstraintCracCreationParameters;
import com.powsybl.openrao.data.crac.io.fbconstraint.xsd.CriticalBranchType;
import com.powsybl.openrao.data.crac.io.fbconstraint.xsd.FlowBasedConstraintDocument;
import com.powsybl.openrao.data.crac.io.fbconstraint.xsd.IndependantComplexVariant;
import com.powsybl.openrao.data.crac.io.commons.RaUsageLimitsAdder;
import com.powsybl.openrao.data.crac.io.commons.ucte.UcteNetworkAnalyzer;
import com.powsybl.openrao.data.crac.io.commons.ucte.UcteNetworkAnalyzerProperties;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static com.google.common.collect.Iterables.isEmpty;
import static com.powsybl.openrao.data.crac.io.commons.ucte.UcteNetworkAnalyzerProperties.BusIdMatchPolicy.COMPLETE_WITH_WILDCARDS;
import static com.powsybl.openrao.data.crac.io.commons.ucte.UcteNetworkAnalyzerProperties.SuffixMatchPriority.NAME_BEFORE_ORDERCODE;

/**
 * @author Viktor Terrier {@literal <viktor.terrier at rte-france.com>}
 * @author Baptiste Seguinot{@literal <baptiste.seguinot at rte-france.com>}
 */
class FbConstraintCracCreator {

    private static void addFbContraintInstants(Crac crac) {
        crac.newInstant("preventive", InstantKind.PREVENTIVE)
            .newInstant("outage", InstantKind.OUTAGE)
            .newInstant("curative", InstantKind.CURATIVE);
    }

    FbConstraintCreationContext createCrac(FlowBasedConstraintDocument fbConstraintDocument, Network network, CracCreationParameters cracCreatorParameters) {
        FbConstraintCracCreationParameters fbConstraintCracCreationParameters = cracCreatorParameters.getExtension(FbConstraintCracCreationParameters.class);
        OffsetDateTime offsetDateTime = null;
        if (fbConstraintCracCreationParameters != null) {
            offsetDateTime = fbConstraintCracCreationParameters.getTimestamp();
        }
        FbConstraintCreationContext creationContext = new FbConstraintCreationContext(offsetDateTime, network.getNameOrId());
        Crac crac = cracCreatorParameters.getCracFactory().create(fbConstraintDocument.getDocumentIdentification().getV(), fbConstraintDocument.getDocumentIdentification().getV(), offsetDateTime);
        addFbContraintInstants(crac);
        RaUsageLimitsAdder.addRaUsageLimits(crac, cracCreatorParameters);

        // check timestamp
        if (!checkTimeStamp(offsetDateTime, fbConstraintDocument.getConstraintTimeInterval().getV(), creationContext)) {
            return creationContext.creationFailure();
        }

        // Check for UCTE network
        if (!network.getSourceFormat().equals("UCTE")) {
            creationContext.getCreationReport().error("FlowBasedConstraintDocument CRAC creation is only possible with a UCTE network");
            return creationContext.creationFailure();
        }

        UcteNetworkAnalyzer ucteNetworkAnalyzer = new UcteNetworkAnalyzer(network, new UcteNetworkAnalyzerProperties(COMPLETE_WITH_WILDCARDS, NAME_BEFORE_ORDERCODE));

        // Store all Outages while reading CriticalBranches and ComplexVariants
        List<OutageReader> outageReaders = new ArrayList<>();

        // read Critical Branches information
        readCriticalBranches(fbConstraintDocument, offsetDateTime, crac, creationContext, ucteNetworkAnalyzer, outageReaders, cracCreatorParameters.getDefaultMonitoredSides());

        // read Complex Variants information
        readComplexVariants(fbConstraintDocument, offsetDateTime, crac, creationContext, ucteNetworkAnalyzer, outageReaders);

        // logs
        creationContext.buildCreationReport();
        return creationContext.creationSucess(crac);
    }

    private void createContingencies(Crac crac, List<OutageReader> outageReaders) {
        outageReaders.forEach(or -> or.addContingency(crac));
    }

    private void readCriticalBranches(FlowBasedConstraintDocument fbConstraintDocument, OffsetDateTime offsetDateTime, Crac crac, FbConstraintCreationContext creationContext, UcteNetworkAnalyzer ucteNetworkAnalyzer, List<OutageReader> outageReaders, Set<TwoSides> defaultMonitoredSides) {
        List<CriticalBranchType> criticalBranchForTimeStamp = selectCriticalBranchesForTimeStamp(fbConstraintDocument, offsetDateTime);

        if (!isEmpty(criticalBranchForTimeStamp)) {
            List<CriticalBranchReader> criticalBranchReaders = criticalBranchForTimeStamp.stream()
                .map(cb -> new CriticalBranchReader(cb, ucteNetworkAnalyzer, defaultMonitoredSides))
                .toList();

            outageReaders.addAll(criticalBranchReaders.stream()
                .filter(CriticalBranchReader::isCriticialBranchValid)
                .filter(cbr -> !cbr.isBaseCase())
                .map(CriticalBranchReader::getOutageReader)
                .toList());

            createContingencies(crac, outageReaders);
            createCnecs(crac, criticalBranchReaders, creationContext);

        } else {
            creationContext.getCreationReport().warn("the flow-based constraint document does not contain any critical branch for the requested timestamp");
        }
        createCnecTimestampFilteringInformation(fbConstraintDocument, offsetDateTime, creationContext);
    }

    private void createCnecs(Crac crac, List<CriticalBranchReader> criticalBranchReaders, FbConstraintCreationContext creationContext) {
        criticalBranchReaders.forEach(criticalBranchReader -> {
            creationContext.addCriticalBranchCreationContext(new CriticalBranchCreationContext(criticalBranchReader, crac));
            if (criticalBranchReader.isCriticialBranchValid()) {
                criticalBranchReader.addCnecs(crac);
            }
        });
    }

    private void createCnecTimestampFilteringInformation(FlowBasedConstraintDocument fbConstraintDocument, OffsetDateTime timestamp, FbConstraintCreationContext creationContext) {
        fbConstraintDocument.getCriticalBranches().getCriticalBranch().stream()
            .filter(criticalBranch -> !isInTimeInterval(timestamp, criticalBranch.getTimeInterval().getV()))
            .filter(criticalBranch -> creationContext.getBranchCnecCreationContext(criticalBranch.getId()) == null)
            .forEach(criticalBranch -> creationContext.addCriticalBranchCreationContext(
                CriticalBranchCreationContext.notImported(criticalBranch.getId(), ImportStatus.NOT_FOR_REQUESTED_TIMESTAMP, "CriticalBranch is not valid for the requested timestamp")
            ));
    }

    private void readComplexVariants(FlowBasedConstraintDocument fbConstraintDocument, OffsetDateTime offsetDateTime, Crac crac, FbConstraintCreationContext creationContext, UcteNetworkAnalyzer ucteNetworkAnalyzer, List<OutageReader> outageReaders) {
        if (Objects.isNull(fbConstraintDocument.getComplexVariants())
            || Objects.isNull(fbConstraintDocument.getComplexVariants().getComplexVariant())
            || fbConstraintDocument.getComplexVariants().getComplexVariant().isEmpty()) {
            creationContext.getCreationReport().warn("the flow-based constraint document does not contain any complex variant");
        } else {
            List<IndependantComplexVariant> remedialActionForTimeStamp = selectRemedialActionsForTimeStamp(fbConstraintDocument, offsetDateTime);
            if (!isEmpty(remedialActionForTimeStamp)) {
                createRemedialAction(crac, ucteNetworkAnalyzer, remedialActionForTimeStamp, outageReaders, creationContext);
            } else {
                creationContext.getCreationReport().warn("the flow-based constraint document does not contain any complex variant for the requested timestamp");
            }
            createRaTimestampFilteringInformation(fbConstraintDocument, offsetDateTime, creationContext);
        }
    }

    private void createRemedialAction(Crac crac, UcteNetworkAnalyzer ucteNetworkAnalyzer, List<IndependantComplexVariant> independantComplexVariants, List<OutageReader> outageReaders, FbConstraintCreationContext creationContext) {

        Set<String> coIds = outageReaders.stream().filter(OutageReader::isOutageValid).map(oR -> oR.getOutage().getId()).collect(Collectors.toSet());

        List<ComplexVariantReader> complexVariantReaders = independantComplexVariants.stream()
            .map(icv -> new ComplexVariantReader(icv, ucteNetworkAnalyzer, coIds))
            .toList();

        ComplexVariantCrossCompatibility.checkAndInvalidate(complexVariantReaders);

        complexVariantReaders.forEach(cvr -> {
            if (cvr.isComplexVariantValid()) {
                cvr.addRemedialAction(crac);
            }
            creationContext.addComplexVariantCreationContext(cvr.getComplexVariantCreationContext());
        });
    }

    private void createRaTimestampFilteringInformation(FlowBasedConstraintDocument fbConstraintDocument, OffsetDateTime timestamp, FbConstraintCreationContext creationContext) {
        fbConstraintDocument.getComplexVariants().getComplexVariant().stream()
             .filter(complexVariant -> !isInTimeInterval(timestamp, complexVariant.getTimeInterval().getV()))
            .filter(complexVariant -> creationContext.getRemedialActionCreationContext(complexVariant.getId()) == null)
            .forEach(complexVariant -> creationContext.addComplexVariantCreationContext(
                new StandardElementaryCreationContext(complexVariant.getId(), null, null, ImportStatus.NOT_FOR_REQUESTED_TIMESTAMP, "ComplexVariant is not valid for the requested timestamp", false)
            ));
    }

    private List<CriticalBranchType> selectCriticalBranchesForTimeStamp(FlowBasedConstraintDocument document, OffsetDateTime timestamp) {

        if (timestamp == null) {
            return document.getCriticalBranches().getCriticalBranch();
        } else {
            List<CriticalBranchType> selectedCriticalBranches = new ArrayList<>();

            document.getCriticalBranches().getCriticalBranch().forEach(criticalBranch -> {
                // Select valid critical branches
                if (isInTimeInterval(timestamp, criticalBranch.getTimeInterval().getV())) {
                    selectedCriticalBranches.add(criticalBranch);
                }
            });
            return selectedCriticalBranches;
        }
    }

    private List<IndependantComplexVariant> selectRemedialActionsForTimeStamp(FlowBasedConstraintDocument document, OffsetDateTime timestamp) {

        if (timestamp == null) {
            return document.getComplexVariants().getComplexVariant();
        } else {
            List<IndependantComplexVariant> selectedRemedialActions = new ArrayList<>();

            document.getComplexVariants().getComplexVariant().forEach(complexVariant -> {
                // Select valid critical branches
                if (isInTimeInterval(timestamp, complexVariant.getTimeInterval().getV())) {
                    selectedRemedialActions.add(complexVariant);
                }
            });
            return selectedRemedialActions;
        }
    }

    private boolean checkTimeStamp(OffsetDateTime offsetDateTime, String fbConstraintDocumentTimeInterval, FbConstraintCreationContext creationContext) {
        if (Objects.isNull(offsetDateTime)) {
            creationContext.getCreationReport().error("when creating a CRAC from a flow-based constraint, timestamp must be non-null");
            return false;
        }

        if (!isInTimeInterval(offsetDateTime, fbConstraintDocumentTimeInterval)) {
            creationContext.getCreationReport().error(String.format("timestamp %s is not in the time interval of the flow-based constraint document: %s", offsetDateTime.toString(), fbConstraintDocumentTimeInterval));
            return false;
        }

        return true;
    }

    private boolean isInTimeInterval(OffsetDateTime offsetDateTime, String timeInterval) {
        String[] timeIntervals = timeInterval.split("/");
        OffsetDateTime startTimeBranch = OffsetDateTime.parse(timeIntervals[0]);
        OffsetDateTime endTimeBranch = OffsetDateTime.parse(timeIntervals[1]);
        // Select valid critical branches
        return !offsetDateTime.isBefore(startTimeBranch) && offsetDateTime.isBefore(endTimeBranch);
    }
}