MetrixGeneratorPostProcessingTimeSeries.java

/*
 * Copyright (c) 2023, 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/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.metrix.integration;

import com.powsybl.metrix.mapping.MappingKey;
import com.powsybl.metrix.mapping.TimeSeriesMappingConfig;
import com.powsybl.timeseries.ast.BinaryOperation;
import com.powsybl.timeseries.ast.IntegerNodeCalc;
import com.powsybl.timeseries.ast.NodeCalc;
import com.powsybl.timeseries.ast.TimeSeriesNameNodeCalc;
import com.powsybl.timeseries.ast.UnaryOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static com.powsybl.metrix.integration.MetrixPostProcessingTimeSeries.CURATIVE_PREFIX;
import static com.powsybl.metrix.integration.MetrixPostProcessingTimeSeries.DOCTRINE_COSTS_ARE_NOT_PROPERLY_CONFIGURED;
import static com.powsybl.metrix.integration.MetrixPostProcessingTimeSeries.PREVENTIVE_PREFIX;
import static com.powsybl.metrix.integration.MetrixPostProcessingTimeSeries.findIdsToProcess;
import static com.powsybl.metrix.integration.dataGenerator.MetrixOutputData.GEN_CUR_PREFIX;
import static com.powsybl.metrix.integration.dataGenerator.MetrixOutputData.GEN_PREFIX;

/**
 * @author Marianne Funfrock {@literal <marianne.funfrock at rte-france.com>}
 */
public final class MetrixGeneratorPostProcessingTimeSeries {

    private static final Logger LOGGER = LoggerFactory.getLogger(MetrixGeneratorPostProcessingTimeSeries.class);

    public static final String PREVENTIVE = "preventive";
    public static final String CURATIVE = "curative";

    public static final String REDISPATCHING = "redispatching";
    public static final String REDISPATCHING_UP = REDISPATCHING + "Up";
    public static final String REDISPATCHING_UP_COST = REDISPATCHING + "UpCost";
    public static final String REDISPATCHING_DOWN = REDISPATCHING + "Down";
    public static final String REDISPATCHING_DOWN_COST = REDISPATCHING + "DownCost";
    public static final String REDISPATCHING_COST = REDISPATCHING + "Cost";

    public static final String PRE_REDISPATCHING_UP_PREFIX = PREVENTIVE_PREFIX + REDISPATCHING_UP;
    public static final String PRE_REDISPATCHING_UP_COST_PREFIX = PREVENTIVE_PREFIX + REDISPATCHING_UP_COST;
    public static final String PRE_REDISPATCHING_DOWN_PREFIX = PREVENTIVE_PREFIX + REDISPATCHING_DOWN;
    public static final String PRE_REDISPATCHING_DOWN_COST_PREFIX = PREVENTIVE_PREFIX + REDISPATCHING_DOWN_COST;
    public static final String PRE_REDISPATCHING_PREFIX = PREVENTIVE_PREFIX + REDISPATCHING;
    public static final String PRE_REDISPATCHING_COST_PREFIX = PREVENTIVE_PREFIX + REDISPATCHING_COST;

    public static final String CUR_REDISPATCHING_UP_PREFIX = CURATIVE_PREFIX + REDISPATCHING_UP;
    public static final String CUR_REDISPATCHING_UP_COST_PREFIX = CURATIVE_PREFIX + REDISPATCHING_UP_COST;
    public static final String CUR_REDISPATCHING_DOWN_PREFIX = CURATIVE_PREFIX + REDISPATCHING_DOWN;
    public static final String CUR_REDISPATCHING_DOWN_COST_PREFIX = CURATIVE_PREFIX + REDISPATCHING_DOWN_COST;
    public static final String CUR_REDISPATCHING_PREFIX = CURATIVE_PREFIX + REDISPATCHING;
    public static final String CUR_REDISPATCHING_COST_PREFIX = CURATIVE_PREFIX + REDISPATCHING_COST;

    public static final GeneratorPostProcessingPrefixContainer PREVENTIVE_PREFIX_CONTAINER = new GeneratorPostProcessingPrefixContainer(
            PREVENTIVE,
            PRE_REDISPATCHING_UP_PREFIX, PRE_REDISPATCHING_UP_COST_PREFIX,
            PRE_REDISPATCHING_DOWN_PREFIX, PRE_REDISPATCHING_DOWN_COST_PREFIX,
            PRE_REDISPATCHING_PREFIX, PRE_REDISPATCHING_COST_PREFIX);
    public static final GeneratorPostProcessingPrefixContainer CURATIVE_PREFIX_CONTAINER = new GeneratorPostProcessingPrefixContainer(
            CURATIVE,
            CUR_REDISPATCHING_UP_PREFIX, CUR_REDISPATCHING_UP_COST_PREFIX,
            CUR_REDISPATCHING_DOWN_PREFIX, CUR_REDISPATCHING_DOWN_COST_PREFIX,
            CUR_REDISPATCHING_PREFIX, CUR_REDISPATCHING_COST_PREFIX);

    private final MetrixDslData metrixDslData;
    private final TimeSeriesMappingConfig mappingConfig;
    private final Set<String> allTimeSeriesNames;
    private final String nullableSchemaName;
    Map<String, NodeCalc> calculatedTimeSeries;

    private final Map<String, NodeCalc> postProcessingTimeSeries = new HashMap<>();

    public MetrixGeneratorPostProcessingTimeSeries(MetrixDslData metrixDslData,
                                                   TimeSeriesMappingConfig mappingConfig,
                                                   Set<String> allTimeSeriesNames,
                                                   String nullableSchemaName) {
        this.metrixDslData = metrixDslData;
        this.mappingConfig = mappingConfig;
        this.allTimeSeriesNames = allTimeSeriesNames;
        this.nullableSchemaName = nullableSchemaName;
        this.calculatedTimeSeries = new HashMap<>(mappingConfig.getTimeSeriesNodes());
    }

    /**
     * Create postprocessing calculated time series for preventive and curative redispatching
     */
    public Map<String, NodeCalc> createPostProcessingTimeSeries() {
        // Preventive
        createRedispatchingPostProcessingTimeSeries(PREVENTIVE_PREFIX_CONTAINER);

        // Curative
        createRedispatchingPostProcessingTimeSeries(CURATIVE_PREFIX_CONTAINER);

        return postProcessingTimeSeries;
    }

    /**
     * For each generator having GEN_generatorId result (generator redispatching time series) (MW)
     * - create up and down redispatching time series (MW)
     * - create up and down costs time series
     * - create global cost time series
     * @param prefixContainer prefix of time series to create (preventive or curative)
     */
    private void createRedispatchingPostProcessingTimeSeries(GeneratorPostProcessingPrefixContainer prefixContainer) {
        String prefix = prefixContainer.postProcessingType().equals(PREVENTIVE) ? GEN_PREFIX : GEN_CUR_PREFIX;
        List<String> generatorIds = findIdsToProcess(metrixDslData.getGeneratorsForRedispatching(), allTimeSeriesNames, prefix);

        // Retrieve doctrine costs time series
        List<String> upCostsTimeSeriesNames = generatorIds.stream().map(id -> mappingConfig.getTimeSeriesName(new MappingKey(MetrixVariable.ON_GRID_DOCTRINE_COST_UP, id))).toList();
        List<String> downCostsTimeSeriesNames = generatorIds.stream().map(id -> mappingConfig.getTimeSeriesName(new MappingKey(MetrixVariable.ON_GRID_DOCTRINE_COST_DOWN, id))).toList();
        if (upCostsTimeSeriesNames.stream().anyMatch(Objects::isNull) || downCostsTimeSeriesNames.stream().anyMatch(Objects::isNull)) {
            LOGGER.warn(DOCTRINE_COSTS_ARE_NOT_PROPERLY_CONFIGURED, "Redispatching");
            return;
        }

        int size = generatorIds.size();
        for (int i = 0; i < size; i++) {
            String generatorId = generatorIds.get(i);
            String upCostsTimeSeriesName = upCostsTimeSeriesNames.get(i);
            String downCostsTimeSeriesName = downCostsTimeSeriesNames.get(i);
            NodeCalc upCostsTimeSeries = calculatedTimeSeries.computeIfAbsent(upCostsTimeSeriesName, TimeSeriesNameNodeCalc::new);
            NodeCalc downCostsTimeSeries = calculatedTimeSeries.computeIfAbsent(downCostsTimeSeriesName, TimeSeriesNameNodeCalc::new);

            // Reference to Metrix redispatching time series result
            NodeCalc genTimeSeries = new TimeSeriesNameNodeCalc(prefix + generatorId);

            // Compute redispatching up, down, cost time series
            createRedispatchingPostProcessingTimeSeries(generatorId, genTimeSeries, upCostsTimeSeries, downCostsTimeSeries, prefixContainer);
        }
    }

    /**
     * Create up and down redispatching calculated time series by decomposing GEN_generatorId
     *    prefix_redispatchingUp_generatorId = GEN_generatorId keeping values >0 and putting 0 otherwise
     *    prefix_redispatchingDown_generatorId = GEN_generatorId keeping values <0 and putting 0 otherwise
     * Create up and down costs calculated time series
     *    prefix_redispatchingUpCost_generatorId = pre_redispatchingUp_generatorId * redispatching up doctrine cost time series
     *    prefix_redispatchingDownCost_generatorId = pre_redispatchingDown_generatorId * redispatching down doctrine cost time series
     * Create global costs calculated time series
     *    prefix_redispatchingCost_generatorId = pre_redispatchingUpCost_generatorId + pre_redispatchingDownCost_generatorId
     * @param generatorId         generator id
     * @param genTimeSeries       GEN_generatorId time series
     * @param upCostsTimeSeries   redispatching up doctrine cost time series
     * @param downCostsTimeSeries redispatching down doctrine cost time series
     * @param prefixContainer     prefix of time series to create (preventive or curative)
     */
    private void createRedispatchingPostProcessingTimeSeries(String generatorId,
                                                             NodeCalc genTimeSeries,
                                                             NodeCalc upCostsTimeSeries,
                                                             NodeCalc downCostsTimeSeries,
                                                             GeneratorPostProcessingPrefixContainer prefixContainer) {
        LOGGER.debug("Creating redispatching postprocessing time-series for {}", generatorId);
        NodeCalc zero = new IntegerNodeCalc(0);

        // Generator up and down redispatching
        NodeCalc genUpPositiveConditionTimeSeries = BinaryOperation.greaterThan(genTimeSeries, zero);
        NodeCalc genDownNegativeConditionTimeSeries = BinaryOperation.lessThan(genTimeSeries, zero);
        NodeCalc genUpTimeSeries = BinaryOperation.multiply(genTimeSeries, genUpPositiveConditionTimeSeries);
        NodeCalc genDownTimeSeries = BinaryOperation.multiply(genTimeSeries, genDownNegativeConditionTimeSeries);
        String genUpTimeSeriesName = MetrixDataName.getNameWithSchema(prefixContainer.redispatchingUpPrefix() + "_" + generatorId, nullableSchemaName);
        String genDownTimeSeriesName = MetrixDataName.getNameWithSchema(prefixContainer.redispatchingDownPrefix() + "_" + generatorId, nullableSchemaName);
        postProcessingTimeSeries.put(genUpTimeSeriesName, genUpTimeSeries);
        postProcessingTimeSeries.put(genDownTimeSeriesName, genDownTimeSeries);

        // Generator up and down redispatching cost
        NodeCalc genUpCostTimeSeries = UnaryOperation.abs(BinaryOperation.multiply(genUpTimeSeries, upCostsTimeSeries));
        NodeCalc genDownCostTimeSeries = UnaryOperation.abs(BinaryOperation.multiply(genDownTimeSeries, downCostsTimeSeries));
        String genUpCostTimeSeriesName = MetrixDataName.getNameWithSchema(prefixContainer.redispatchingUpCostPrefix() + "_" + generatorId, nullableSchemaName);
        String genDownCostTimeSeriesName = MetrixDataName.getNameWithSchema(prefixContainer.redispatchingDownCostPrefix() + "_" + generatorId, nullableSchemaName);
        postProcessingTimeSeries.put(genUpCostTimeSeriesName, genUpCostTimeSeries);
        postProcessingTimeSeries.put(genDownCostTimeSeriesName, genDownCostTimeSeries);

        // Generator global redispatching cost = up cost + down cost
        NodeCalc genCostTimeSeries = BinaryOperation.plus(genUpCostTimeSeries, genDownCostTimeSeries);
        String genCostTimeSeriesName = MetrixDataName.getNameWithSchema(prefixContainer.redispatchingCostPrefix() + "_" + generatorId, nullableSchemaName);
        postProcessingTimeSeries.put(genCostTimeSeriesName, genCostTimeSeries);
    }
}