Crac.java

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

import com.powsybl.commons.util.ServiceLoaderCache;
import com.powsybl.contingency.Contingency;
import com.powsybl.iidm.network.Network;
import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.commons.PhysicalParameter;
import com.powsybl.openrao.data.crac.api.cnec.AngleCnec;
import com.powsybl.openrao.data.crac.api.cnec.AngleCnecAdder;
import com.powsybl.openrao.data.crac.api.cnec.BranchCnec;
import com.powsybl.openrao.data.crac.api.cnec.Cnec;
import com.powsybl.openrao.data.crac.api.cnec.FlowCnec;
import com.powsybl.openrao.data.crac.api.cnec.FlowCnecAdder;
import com.powsybl.openrao.data.crac.api.cnec.VoltageCnec;
import com.powsybl.openrao.data.crac.api.cnec.VoltageCnecAdder;
import com.powsybl.openrao.data.crac.api.io.Exporter;
import com.powsybl.openrao.data.crac.api.io.Importer;
import com.powsybl.openrao.data.crac.api.parameters.CracCreationParameters;
import com.powsybl.openrao.data.crac.api.rangeaction.CounterTradeRangeAction;
import com.powsybl.openrao.data.crac.api.rangeaction.CounterTradeRangeActionAdder;
import com.powsybl.openrao.data.crac.api.rangeaction.HvdcRangeAction;
import com.powsybl.openrao.data.crac.api.rangeaction.HvdcRangeActionAdder;
import com.powsybl.openrao.data.crac.api.rangeaction.InjectionRangeAction;
import com.powsybl.openrao.data.crac.api.rangeaction.InjectionRangeActionAdder;
import com.powsybl.openrao.data.crac.api.rangeaction.PstRangeAction;
import com.powsybl.openrao.data.crac.api.rangeaction.PstRangeActionAdder;
import com.powsybl.openrao.data.crac.api.rangeaction.RangeAction;
import com.powsybl.openrao.data.crac.api.usagerule.UsageMethod;
import com.powsybl.openrao.data.crac.api.networkaction.NetworkAction;
import com.powsybl.openrao.data.crac.api.networkaction.NetworkActionAdder;

import java.io.*;
import java.time.OffsetDateTime;
import java.util.*;

import static java.lang.String.format;

/**
 * Interface to manage CRAC.
 * CRAC stands for Contingency list, Remedial Actions and additional Constraints
 * <p>
 * It involves:
 * <ul>
 *     <li>{@link Instant} objects</li>
 *     <li>{@link Contingency} objects</li>
 *     <li>{@link State} objects: one of them represents the network without contingency applied and can be accessed with the getPreventiveState method</li>
 *     <li>{@link Cnec} objects</li>
 *     <li>{@link RangeAction} objects</li>
 *     <li>{@link NetworkAction} objects</li>
 * </ul>
 *
 * @author Joris Mancini {@literal <joris.mancini at rte-france.com>}
 */
public interface Crac extends Identifiable<Crac> {

    /**
     * Get the timestamp of the Crac.
     */
    Optional<OffsetDateTime> getTimestamp();

    // Contingencies management

    /**
     * Get a {@link ContingencyAdder}, to add a contingency to the Crac
     */
    ContingencyAdder newContingency();

    /**
     * Gather all the contingencies present in the Crac. It returns a set because contingencies
     * must not be duplicated and there is no defined order for contingencies.
     */
    Set<Contingency> getContingencies();

    /**
     * Get a contingency given its id. Returns null if the contingency does not exist.
     */
    Contingency getContingency(String id);

    /**
     * Remove a contingency - identified by its id - from the Crac
     */
    void removeContingency(String id);

    // Instants management

    /**
     * Add instant
     *
     * @return crac
     */
    Crac newInstant(String instantId, InstantKind instantKind);

    /**
     * Get instant based on Id
     * If the ID is null, this will return a null instant
     */
    Instant getInstant(String instantId);

    /**
     * Gather all the instants present in the Crac. It returns a list of ordered instants
     */
    List<Instant> getSortedInstants();

    /**
     * Get instant based on a kind. Throws exception:
     * - if crac does not contain such instant Kind or
     * - if multiple instants of this kind are defined in the crac
     */
    Instant getInstant(InstantKind instantKind);

    /**
     * Returns all the instants present in the Crac with the correct instantKind.
     */
    SortedSet<Instant> getInstants(InstantKind instantKind);

    /**
     * Returns the previous instant of an instant.
     * Optional is empty if no previous instant is defined.
     */
    Instant getInstantBefore(Instant providedInstant);

    /**
     * Returns the unique preventive instant
     */
    Instant getPreventiveInstant();

    /**
     * Returns the unique outage instant
     */
    Instant getOutageInstant();

    /**
     * Return the last instant, i.e. the only instant that has no instant after it
     */
    Instant getLastInstant();

    /**
     * Returns whether a crac has an auto instant
     */
    boolean hasAutoInstant();

    // States management

    /**
     * Gather all the states present in the Crac. It returns a set because states must not
     * be duplicated and there is no defined order for states.
     */
    Set<State> getStates();

    /**
     * Select the preventive state. This state is unique. It's the only state that is
     * defined on the preventive instant, with no contingency.
     */
    State getPreventiveState();

    /**
     * Get the curative states for all curative instants.
     */
    Set<State> getCurativeStates();

    /**
     * Chronological list of states after a defined contingency. The chronology is defined by
     * instants objects. This is a set because states must not be duplicated and it is sorted
     * by chronology of instants. Can return null if no matching contingency is found.
     *
     * @param contingency: The contingency after which we want to gather states.
     * @return Ordered set of states after the specified contingency.
     */
    SortedSet<State> getStates(Contingency contingency);

    /**
     * Unordered set of States defined at the same instant. It will be either the preventive state or
     * the set of all the states defined at the same instant after all the contingencies. It is a set
     * because states must not be duplicated and there is no defined order for states selected by
     * instants. Can return null if no matching instant is found.
     *
     * @param instant: The instant at which we want to gather states.
     * @return Unordered set of states at the same specified instant.
     */
    Set<State> getStates(Instant instant);

    /**
     * Select a unique state after a contingency and at a specific instant.
     * Can return null if no matching state or contingency are found.
     *
     * @param contingency: The contingency after which we want to select the state.
     * @param instant:     The instant at which we want to select the state.
     * @return State after a contingency and at a specific instant.
     */
    State getState(Contingency contingency, Instant instant);

    /**
     * Unordered set of States defined at the same instant. It will be either the preventive state or
     * the set of all the states defined at the same instant after all the contingencies. It is a set
     * because states must not be duplicated and there is no defined order for states selected by
     * instants. Can return null if no matching instant is found.
     *
     * @param instant: The instant at which we want to gather states.
     * @return Unordered set of states at the same specified instant.
     */
    default Set<State> getStatesFromInstant(Instant instant) {
        return getStates(instant);
    }

    /**
     * Chronological list of states after a defined contingency. The chronology is defined by
     * instants objects. This is a set because states must not be duplicated and it is sorted
     * by chronology of instants. Can return null if no matching contingency is found.
     *
     * @param id: The contingency id after which we want to gather states.
     * @return Ordered set of states after the specified contingency.
     */
    default SortedSet<State> getStatesFromContingency(String id) {
        if (getContingency(id) != null) {
            return getStates(getContingency(id));
        } else {
            return new TreeSet<>();
        }
    }

    /**
     * Select a unique state after a contingency and at a specific instant, specified by their ids.
     *
     * @param contingencyId: The contingency id after which we want to select the state.
     * @param instant:       The instant at which we want to select the state.
     * @return State after a contingency and at a specific instant. Can return null if no matching
     * state or contingency are found.
     */
    default State getState(String contingencyId, Instant instant) {
        Objects.requireNonNull(contingencyId, "Contingency ID should be defined.");
        Objects.requireNonNull(instant, "Instant should be defined.");
        if (getContingency(contingencyId) == null) {
            throw new OpenRaoException(format("Contingency %s does not exist, as well as the related state.", contingencyId));
        }
        return getState(getContingency(contingencyId), instant);
    }

    // Cnecs management

    /**
     * Get a {@link FlowCnecAdder} adder, to add a {@link FlowCnec} to the Crac
     */
    FlowCnecAdder newFlowCnec();

    /**
     * Get an {@link AngleCnecAdder} adder, to add an {@link AngleCnec} to the Crac
     */
    AngleCnecAdder newAngleCnec();

    /**
     * Get a {@link VoltageCnecAdder} adder, to add a {@link VoltageCnec} to the Crac
     */
    VoltageCnecAdder newVoltageCnec();

    /**
     * Gather all the Cnecs present in the Crac. It returns a set because Cnecs must not
     * be duplicated and there is no defined order for Cnecs.
     */
    Set<Cnec> getCnecs();

    /**
     * Gather all the Cnecs of a specified State. It returns a set because Cnecs
     * must not be duplicated and there is no defined order for Cnecs.
     */
    Set<Cnec> getCnecs(State state);

    Set<Cnec> getCnecs(PhysicalParameter physicalParameter);

    Set<Cnec> getCnecs(PhysicalParameter physicalParameter, State state);

    /**
     * Find a Cnec by its id, returns null if the Cnec does not exists
     */
    Cnec getCnec(String cnecId);

    /**
     * Gather all the BranchCnecs present in the Crac. It returns a set because Cnecs
     * must not be duplicated and there is no defined order for Cnecs.
     *
     * @deprecated consider using getCnecs() or getFlowCnecs() instead
     */
    // keep the method (might be useful when we will have other BranchCnec than FlowCnec)
    @Deprecated(since = "3.0.0")
    Set<BranchCnec> getBranchCnecs();

    /**
     * Gather all the BranchCnecs of a specified State. It returns a set because Cnecs
     * must not be duplicated and there is no defined order for Cnecs.
     *
     * @deprecated consider using getCnecs() or getFlowCnecs() instead
     */
    // keep the method (might be useful when we will have other BranchCnec than FlowCnec)
    @Deprecated(since = "3.0.0")
    Set<BranchCnec> getBranchCnecs(State state);

    /**
     * Find a BranchCnec by its id, returns null if the BranchCnec does not exists
     *
     * @deprecated consider using getCnec() or getFlowCnec() instead
     */
    // keep the method (might be usefuls when we will have other BranchCnec than FlowCnec)
    @Deprecated(since = "3.0.0")
    BranchCnec getBranchCnec(String branchCnecId);

    /**
     * Gather all the FlowCnecs present in the Crac. It returns a set because Cnecs must not
     * be duplicated and there is no defined order for Cnecs.
     */
    Set<FlowCnec> getFlowCnecs();

    /**
     * Gather all the FlowCnecs of a specified State. It returns a set because Cnecs must not be
     * duplicated and there is no defined order for Cnecs.
     */
    Set<FlowCnec> getFlowCnecs(State state);

    /**
     * Find a FlowCnec by its id, returns null if the FlowCnec does not exist.
     */
    FlowCnec getFlowCnec(String flowCnecId);

    /**
     * Gather all the AngleCnecs present in the Crac. It returns a set because Cnecs must not
     * be duplicated and there is no defined order for Cnecs.
     */
    Set<AngleCnec> getAngleCnecs();

    /**
     * Gather all the AngleCnecs of a specified State. It returns a set because Cnecs must not be
     * duplicated and there is no defined order for Cnecs.
     */
    Set<AngleCnec> getAngleCnecs(State state);

    /**
     * Find an AngleCnec by its id, returns null if the AngleCnec does not exist.
     */
    AngleCnec getAngleCnec(String angleCnecId);

    /**
     * Gather all the VoltageCnecs present in the Crac. It returns a set because Cnecs must not
     * be duplicated and there is no defined order for Cnecs.
     */
    Set<VoltageCnec> getVoltageCnecs();

    /**
     * Gather all the VoltageCnecs of a specified State. It returns a set because Cnecs must not be
     * duplicated and there is no defined order for Cnecs.
     */
    Set<VoltageCnec> getVoltageCnecs(State state);

    /**
     * Find a VoltageCnec by its id, returns null if the VoltageCnec does not exist.
     */
    VoltageCnec getVoltageCnec(String voltageCnecId);

    /**
     * Remove a Cnec - identified by its id - from the Crac
     */
    void removeCnec(String cnecId);

    /**
     * Remove a FlowCnec - identified by its id - from the Crac
     */
    void removeFlowCnec(String flowCnecId);

    /**
     * Remove a set of FlowCnecs - identified by their id - from the Crac
     */
    void removeFlowCnecs(Set<String> flowCnecsIds);

    /**
     * Remove an AngleCnec - identified by its id - from the Crac
     */
    void removeAngleCnec(String angleCnecId);

    /**
     * Remove a set of AngleCnecs - identified by their id - from the Crac
     */
    void removeAngleCnecs(Set<String> angleCnecsIds);

    /**
     * Remove a VoltageCnec - identified by its id - from the Crac
     */
    void removeVoltageCnec(String voltageCnecId);

    /**
     * Remove a set of VoltageCnecs - identified by their id - from the Crac
     */
    void removeVoltageCnecs(Set<String> voltageCnecsIds);

    // Remedial actions management

    /**
     * Gather all the remedial actions present in the Crac. It returns a set because remedial
     * actions must not be duplicated and there is no defined order for remedial actions.
     */
    Set<RemedialAction<?>> getRemedialActions();

    /**
     * Find a remedial action by its id, returns null if the remedial action does not exists
     */
    RemedialAction<?> getRemedialAction(String remedialActionId);

    /**
     * Remove a remedial action - identified by its id - from the Crac
     */
    void removeRemedialAction(String id);

    // Range actions management

    /**
     * Get a {@link PstRangeActionAdder}, to add a {@link PstRangeAction} to the crac
     */
    PstRangeActionAdder newPstRangeAction();

    /**
     * Get a {@link HvdcRangeActionAdder}, to add a {@link HvdcRangeAction} to the crac
     */
    HvdcRangeActionAdder newHvdcRangeAction();

    /**
     * Get a {@link InjectionRangeActionAdder}, to add an {@link InjectionRangeAction} to the crac
     */
    InjectionRangeActionAdder newInjectionRangeAction();


    /**
     * Get a {@link CounterTradeRangeActionAdder}, to add an {@link CounterTradeRangeAction} to the crac
     */
    CounterTradeRangeActionAdder newCounterTradeRangeAction();

    /**
     * Gather all the range actions present in the Crac. It returns a set because range
     * actions must not be duplicated and there is no defined order for range actions.
     */
    Set<RangeAction<?>> getRangeActions();

    /**
     * Gather all the range actions of a specified state with one of the specified usage methods
     */
    Set<RangeAction<?>> getRangeActions(State state, UsageMethod... usageMethod);

    /**
     * Gather all the network actions of a specified state that are potentially available
     */
    Set<RangeAction<?>> getPotentiallyAvailableRangeActions(State state);

    default boolean isRangeActionPreventive(RangeAction<?> rangeAction) {
        return isRangeActionAvailableInState(rangeAction, getPreventiveState());
    }

    default boolean isRangeActionAutoOrCurative(RangeAction<?> rangeAction) {
        return getStates().stream()
            .filter(state -> state.getInstant().isAuto() || state.getInstant().isCurative())
            .anyMatch(state -> isRangeActionAvailableInState(rangeAction, state));
    }

    default boolean isRangeActionAvailableInState(RangeAction<?> rangeAction, State state) {
        return getPotentiallyAvailableRangeActions(state).contains(rangeAction);
    }

    /**
     * Find a range action by its id, returns null if the range action does not exists
     */
    RangeAction<?> getRangeAction(String id);

    /**
     * Gather all the PstRangeAction present in the Crac. It returns a set because remedial
     * actions must not be duplicated and there is no defined order for remedial actions.
     */
    Set<PstRangeAction> getPstRangeActions();

    /**
     * Gather all the HvdcRangeAction present in the Crac. It returns a set because remedial
     * actions must not be duplicated and there is no defined order for remedial actions.
     */
    Set<HvdcRangeAction> getHvdcRangeActions();

    /**
     * Gather all the InjectionRangeAction present in the Crac. It returns a set because remedial
     * actions must not be duplicated and there is no defined order for remedial actions.
     */
    Set<InjectionRangeAction> getInjectionRangeActions();

    /**
     * Gather all the CounterTradeRangeAction present in the Crac. It returns a set because remedial
     * actions must not be duplicated and there is no defined order for remedial actions.
     */
    Set<CounterTradeRangeAction> getCounterTradeRangeActions();

    /**
     * Find a PstRangeAction by its id, returns null if the remedial action does not exists
     */
    PstRangeAction getPstRangeAction(String pstRangeActionId);

    /**
     * Find a HvdcRangeAction by its id, returns null if the remedial action does not exists
     */
    HvdcRangeAction getHvdcRangeAction(String hvdcRangeActionId);

    /**
     * Find an InjectionRangeAction by its id, returns null if the remedial action does not exists
     */
    InjectionRangeAction getInjectionRangeAction(String injectionRangeActionId);

    /**
     * Find a CounterTradeRangeAction by its id, returns null if the remedial action does not exists
     */
    CounterTradeRangeAction getCounterTradeRangeAction(String counterTradeRangeActionId);

    /**
     * Remove a PstRangeAction - identified by its id - from the Crac
     */
    void removePstRangeAction(String id);

    /**
     * Remove a HvdcRangeAction - identified by its id - from the Crac
     */
    void removeHvdcRangeAction(String id);

    /**
     * Remove an InjectionRangeAction - identified by its id - from the Crac
     */
    void removeInjectionRangeAction(String id);


    // Network actions management

    /**
     * Get a {@link NetworkActionAdder}, to add a {@link NetworkAction} to the crac
     */
    NetworkActionAdder newNetworkAction();

    /**
     * Gather all the network actions present in the Crac. It returns a set because network
     * actions must not be duplicated and there is no defined order for network actions.
     */
    Set<NetworkAction> getNetworkActions();

    /**
     * Gather all the network actions of a specified state with one of the specified usage methods
     */
    Set<NetworkAction> getNetworkActions(State state, UsageMethod... usageMethod);

    /**
     * Gather all the network actions of a specified state that are potentially available
     */
    Set<NetworkAction> getPotentiallyAvailableNetworkActions(State state);

    /**
     * Find a NetworkAction by its id, returns null if the network action does not exists
     */
    NetworkAction getNetworkAction(String id);

    /**
     * Remove a NetworkAction - identified by its id - from the Crac
     */
    void removeNetworkAction(String id);

    /**
     * Get all remedial action usage limitation according to the crac creation parameters
     */
    Map<Instant, RaUsageLimits> getRaUsageLimitsPerInstant();

    /**
     * Get remedial action usage limitation for a given instant
     */
    RaUsageLimits getRaUsageLimits(Instant instant);

    /**
     * Get a {@link RaUsageLimitsAdder}, to add a {@link RaUsageLimits} to the crac
     */
    RaUsageLimitsAdder newRaUsageLimits(String instantName);

    /**
     * Get the CRAC format
     *
     * @param filename    CRAC file name
     * @param inputStream CRAC data
     * @return the CRAC format (if found)
     */
    static String getCracFormat(String filename, InputStream inputStream) throws IOException {
        byte[] bytes = getBytesFromInputStream(inputStream);
        return findImporter(filename, bytes).getFormat();
    }

    /**
     * Import CRAC from a file, inside a CracCreationContext
     *
     * @param filename               CRAC file name
     * @param inputStream            CRAC data
     * @param network                the network on which the CRAC data is based
     * @param cracCreationParameters extra CRAC creation parameters
     * @return CracCreationContext object
     */
    static CracCreationContext readWithContext(String filename, InputStream inputStream, Network network, CracCreationParameters cracCreationParameters) throws IOException {
        byte[] bytes = getBytesFromInputStream(inputStream);
        return findImporter(filename, bytes).importData(new ByteArrayInputStream(bytes), cracCreationParameters, network);
    }

    private static Importer findImporter(String filename, byte[] bytes) {
        return new ServiceLoaderCache<>(Importer.class).getServices().stream()
            .filter(importer -> importer.exists(filename, new ByteArrayInputStream(bytes)))
            .findAny()
            .orElseThrow(() -> new OpenRaoException("No suitable CRAC importer found."));
    }

    /**
     * Import CRAC from a file, inside a CracCreationContext
     *
     * @param filename    CRAC file name
     * @param inputStream CRAC data
     * @param network     the network on which the CRAC data is based
     * @return CracCreationContext object
     */
    static CracCreationContext readWithContext(String filename, InputStream inputStream, Network network) throws IOException {
        return readWithContext(filename, inputStream, network, CracCreationParameters.load());
    }

    /**
     * Import CRAC from a file
     *
     * @param filename               CRAC file name
     * @param inputStream            CRAC data
     * @param network                the network on which the CRAC data is based
     * @param cracCreationParameters extra CRAC creation parameters
     * @return CRAC object
     */
    static Crac read(String filename, InputStream inputStream, Network network, CracCreationParameters cracCreationParameters) throws IOException {
        return readWithContext(filename, inputStream, network, cracCreationParameters).getCrac();
    }

    /**
     * Import CRAC from a file
     *
     * @param filename    CRAC file name
     * @param inputStream CRAC data
     * @param network     the network on which the CRAC data is based
     * @return CRAC object
     */
    static Crac read(String filename, InputStream inputStream, Network network) throws IOException {
        return read(filename, inputStream, network, CracCreationParameters.load());
    }

    private static byte[] getBytesFromInputStream(InputStream inputStream) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        org.apache.commons.io.IOUtils.copy(inputStream, baos);
        return baos.toByteArray();
    }

    /**
     * Write CRAC data into a file
     *
     * @param exporters    candidate CRAC exporters
     * @param format       desired output CRAC data type
     * @param outputStream file where to write the CRAC data
     */
    private void write(List<Exporter> exporters, String format, OutputStream outputStream) {
        exporters.stream()
            .filter(ex -> format.equals(ex.getFormat()))
            .findAny()
            .orElseThrow(() -> new OpenRaoException("Export format " + format + " not supported"))
            .exportData(this, outputStream);
    }

    /**
     * Write CRAC data into a file
     *
     * @param format       desired output CRAC data type
     * @param outputStream file where to write the CRAC data
     */
    default void write(String format, OutputStream outputStream) {
        write(new ServiceLoaderCache<>(Exporter.class).getServices(), format, outputStream);
    }
}