FlowCnecResultArraySerializer.java

/*
 *  Copyright (c) 2021, 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.json.serializers;

import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.commons.Unit;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.Instant;
import com.powsybl.openrao.data.crac.api.InstantKind;
import com.powsybl.openrao.data.crac.api.cnec.FlowCnec;
import com.powsybl.iidm.network.TwoSides;
import com.powsybl.openrao.data.raoresult.io.json.RaoResultJsonConstants;
import com.powsybl.openrao.data.raoresult.api.RaoResult;
import com.fasterxml.jackson.core.JsonGenerator;

import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

import static com.powsybl.openrao.commons.MeasurementRounding.roundValueBasedOnMargin;
import static com.powsybl.openrao.commons.Unit.AMPERE;
import static com.powsybl.openrao.commons.Unit.MEGAWATT;

/**
 * @author Baptiste Seguinot {@literal <baptiste.seguinot at rte-france.com>}
 */
final class FlowCnecResultArraySerializer {

    private FlowCnecResultArraySerializer() {
    }

    static void serialize(RaoResult raoResult, Crac crac, Set<Unit> flowUnits, JsonGenerator jsonGenerator) throws IOException {

        List<FlowCnec> sortedListOfFlowCnecs = crac.getFlowCnecs().stream()
            .sorted(Comparator.comparing(FlowCnec::getId))
            .toList();

        jsonGenerator.writeArrayFieldStart(RaoResultJsonConstants.FLOWCNEC_RESULTS);
        for (FlowCnec flowCnec : sortedListOfFlowCnecs) {
            if (!flowCnec.getId().contains("OUTAGE DUPLICATE")) {
                serializeFlowCnecResult(flowCnec, raoResult, crac, flowUnits, jsonGenerator);
            }
        }
        jsonGenerator.writeEndArray();
    }

    private static void serializeFlowCnecResult(FlowCnec flowCnec, RaoResult raoResult, Crac crac, Set<Unit> flowUnits, JsonGenerator jsonGenerator) throws IOException {
        if (!containsAnyResultForFlowCnec(raoResult, flowCnec, crac, MEGAWATT) && !containsAnyResultForFlowCnec(raoResult, flowCnec, crac, AMPERE)) {
            return;
        }
        jsonGenerator.writeStartObject();
        jsonGenerator.writeStringField(RaoResultJsonConstants.FLOWCNEC_ID, flowCnec.getId());

        serializeFlowCnecResultForOptimizationState(null, flowCnec, raoResult, crac, flowUnits, jsonGenerator);
        serializeFlowCnecResultForOptimizationState(crac.getPreventiveInstant(), flowCnec, raoResult, crac, flowUnits, jsonGenerator);
        Instant instant = flowCnec.getState().getInstant();
        if (instant.isCurative() || instant.isAuto()) {
            if (crac.hasAutoInstant()) {
                serializeFlowCnecResultForOptimizationState(crac.getInstant(InstantKind.AUTO), flowCnec, raoResult, crac, flowUnits, jsonGenerator);
            }
            crac.getInstants(InstantKind.CURATIVE).forEach(curativeInstant -> {
                if (!curativeInstant.comesAfter(instant)) {
                    try {
                        serializeFlowCnecResultForOptimizationState(curativeInstant, flowCnec, raoResult, crac, flowUnits, jsonGenerator);
                    } catch (IOException e) {
                        throw new OpenRaoException("An error occurred when serializing FlowCNEC results", e);
                    }
                }
            });
        }
        jsonGenerator.writeEndObject();
    }

    private static void serializeFlowCnecResultForOptimizationState(Instant optInstant, FlowCnec flowCnec, RaoResult raoResult, Crac crac, Set<Unit> flowUnits, JsonGenerator jsonGenerator) throws IOException {
        if (!containsAnyResultForOptimizationState(raoResult, flowCnec, optInstant, MEGAWATT) && !containsAnyResultForOptimizationState(raoResult, flowCnec, optInstant, AMPERE)) {
            return;
        }
        jsonGenerator.writeObjectFieldStart(RaoResultJsonConstants.serializeInstantId(optInstant));
        for (Unit flowUnit : flowUnits.stream().sorted().toList()) {
            serializeFlowCnecResultForOptimizationStateAndUnit(optInstant, flowUnit, flowCnec, raoResult, crac, jsonGenerator);
        }
        jsonGenerator.writeEndObject();
    }

    private static void serializeFlowCnecResultForOptimizationStateAndUnit(Instant optInstant, Unit unit, FlowCnec flowCnec, RaoResult raoResult, Crac crac, JsonGenerator jsonGenerator) throws IOException {
        if (!containsAnyResultForFlowCnec(raoResult, flowCnec, crac, unit)) {
            return;
        }
        jsonGenerator.writeObjectFieldStart(RaoResultJsonConstants.serializeUnit(unit));
        serializeFlowCnecMargin(optInstant, unit, flowCnec, raoResult, jsonGenerator);
        for (TwoSides side : flowCnec.getMonitoredSides().stream().sorted(Comparator.comparing(TwoSides::toString)).toList()) {
            serializeFlowCnecFlows(optInstant, unit, flowCnec, side, raoResult, jsonGenerator);
        }
        jsonGenerator.writeEndObject();
    }

    private static void serializeFlowCnecMargin(Instant optInstant, Unit unit, FlowCnec flowCnec, RaoResult raoResult, JsonGenerator jsonGenerator) throws IOException {
        double margin = safeGetMargin(raoResult, flowCnec, optInstant, unit);
        double relativeMargin = safeGetRelativeMargin(raoResult, flowCnec, optInstant, unit);

        if (Double.isNaN(margin) && Double.isNaN(relativeMargin)) {
            return;
        }
        if (!Double.isNaN(margin)) {
            jsonGenerator.writeNumberField(RaoResultJsonConstants.MARGIN, roundValueBasedOnMargin(margin, margin, 2));
        }
        if (!Double.isNaN(relativeMargin)) {
            jsonGenerator.writeNumberField(RaoResultJsonConstants.RELATIVE_MARGIN, roundValueBasedOnMargin(relativeMargin, margin, 2));
        }
    }

    private static void serializeFlowCnecFlows(Instant optInstant, Unit unit, FlowCnec flowCnec, TwoSides side, RaoResult raoResult, JsonGenerator jsonGenerator) throws IOException {
        double flow = safeGetFlow(raoResult, flowCnec, side, optInstant, unit);
        double margin = safeGetMargin(raoResult, flowCnec, optInstant, unit);
        double loopFlow = safeGetLoopFlow(raoResult, flowCnec, side, optInstant, unit);
        double commercialFlow = safeGetCommercialFlow(raoResult, flowCnec, side, optInstant, unit);
        double ptdfZonalSum = safeGetPtdfZonalSum(raoResult, flowCnec, side, optInstant);

        if (Double.isNaN(flow) && Double.isNaN(loopFlow) && Double.isNaN(commercialFlow) && (!unit.equals(MEGAWATT) || Double.isNaN(ptdfZonalSum))) {
            return;
        }

        jsonGenerator.writeObjectFieldStart(RaoResultJsonConstants.serializeSide(side));
        if (!Double.isNaN(flow)) {
            jsonGenerator.writeNumberField(RaoResultJsonConstants.FLOW, roundValueBasedOnMargin(flow, margin, 2));
        }
        if (!Double.isNaN(loopFlow)) {
            jsonGenerator.writeNumberField(RaoResultJsonConstants.LOOP_FLOW, roundValueBasedOnMargin(loopFlow, margin, 2));
        }
        if (!Double.isNaN(commercialFlow)) {
            jsonGenerator.writeNumberField(RaoResultJsonConstants.COMMERCIAL_FLOW, roundValueBasedOnMargin(commercialFlow, margin, 2));
        }
        if (unit.equals(MEGAWATT) && !Double.isNaN(ptdfZonalSum)) {
            jsonGenerator.writeNumberField(RaoResultJsonConstants.ZONAL_PTDF_SUM, roundValueBasedOnMargin(ptdfZonalSum, margin, 6));
        }
        jsonGenerator.writeEndObject();
    }

    private static boolean containsAnyResultForFlowCnec(RaoResult raoResult, FlowCnec flowCnec, Crac crac, Unit unit) {
        if (flowCnec.getState().isPreventive()) {
            return containsAnyResultForOptimizationState(raoResult, flowCnec, null, unit) ||
                containsAnyResultForOptimizationState(raoResult, flowCnec, flowCnec.getState().getInstant(), unit);
        } else {
            return containsAnyResultForOptimizationState(raoResult, flowCnec, null, unit) ||
                containsAnyResultForOptimizationState(raoResult, flowCnec, crac.getPreventiveInstant(), unit) ||
                crac.hasAutoInstant() && containsAnyResultForOptimizationState(raoResult, flowCnec, crac.getInstant(InstantKind.AUTO), unit) ||
                crac.getInstants(InstantKind.CURATIVE).stream().anyMatch(curativeInstant -> containsAnyResultForOptimizationState(raoResult, flowCnec, curativeInstant, unit));
        }
    }

    private static boolean containsAnyResultForOptimizationState(RaoResult raoResult, FlowCnec flowCnec, Instant optInstant, Unit unit) {
        return !Double.isNaN(safeGetMargin(raoResult, flowCnec, optInstant, unit)) ||
            !Double.isNaN(safeGetRelativeMargin(raoResult, flowCnec, optInstant, unit)) ||
            containsAnyResultForOptimizationStateAndSide(raoResult, flowCnec, TwoSides.ONE, optInstant, unit) ||
            containsAnyResultForOptimizationStateAndSide(raoResult, flowCnec, TwoSides.TWO, optInstant, unit);
    }

    private static boolean containsAnyResultForOptimizationStateAndSide(RaoResult raoResult, FlowCnec flowCnec, TwoSides side, Instant optInstant, Unit unit) {
        return !Double.isNaN(safeGetFlow(raoResult, flowCnec, side, optInstant, unit)) ||
            !Double.isNaN(safeGetLoopFlow(raoResult, flowCnec, side, optInstant, unit)) ||
            !Double.isNaN(safeGetCommercialFlow(raoResult, flowCnec, side, optInstant, unit)) ||
            !Double.isNaN(safeGetPtdfZonalSum(raoResult, flowCnec, side, optInstant)) && unit.equals(MEGAWATT);
    }

    private static double safeGetFlow(RaoResult raoResult, FlowCnec flowCnec, TwoSides side, Instant optInstant, Unit unit) {
        // methods getFlow can return an exception if RAO is executed on one state only
        try {
            return raoResult.getFlow(optInstant, flowCnec, side, unit);
        } catch (OpenRaoException e) {
            return Double.NaN;
        }
    }

    private static double safeGetMargin(RaoResult raoResult, FlowCnec flowCnec, Instant optInstant, Unit unit) {
        // methods getMargin can return an exception if RAO is executed on one state only
        try {
            return raoResult.getMargin(optInstant, flowCnec, unit);
        } catch (OpenRaoException e) {
            return Double.NaN;
        }
    }

    private static double safeGetRelativeMargin(RaoResult raoResult, FlowCnec flowCnec, Instant optInstant, Unit unit) {
        // methods getRelativeMargin can return an exception if RAO is executed on one state only
        try {
            return raoResult.getRelativeMargin(optInstant, flowCnec, unit);
        } catch (OpenRaoException e) {
            return Double.NaN;
        }
    }

    private static double safeGetLoopFlow(RaoResult raoResult, FlowCnec flowCnec, TwoSides side, Instant optInstant, Unit unit) {
        // methods getLoopFlow can throw an exception if queried in AMPERE
        try {
            return raoResult.getLoopFlow(optInstant, flowCnec, side, unit);
        } catch (OpenRaoException e) {
            return Double.NaN;
        }
    }

    private static double safeGetCommercialFlow(RaoResult raoResult, FlowCnec flowCnec, TwoSides side, Instant optInstant, Unit unit) {
        // methods getCommercialFlow can throw an exception if queried in AMPERE
        try {
            return raoResult.getCommercialFlow(optInstant, flowCnec, side, unit);
        } catch (OpenRaoException e) {
            return Double.NaN;
        }
    }

    private static double safeGetPtdfZonalSum(RaoResult raoResult, FlowCnec flowCnec, TwoSides side, Instant optInstant) {
        // methods getPtdfZonalSum can throw an exception if RAO is executed on one state only
        try {
            return raoResult.getPtdfZonalSum(optInstant, flowCnec, side);
        } catch (OpenRaoException e) {
            return Double.NaN;
        }
    }
}