MetrixNetworkPoint.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/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.metrix.integration.network;

import com.powsybl.iidm.network.*;
import com.powsybl.metrix.mapping.TimeSeriesMapper;
import com.powsybl.timeseries.*;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author Valentin Berthault {@literal <valentin.berthault at rte-france.com>}
 */
public final class MetrixNetworkPoint {

    private static final String ID_TO_CLOSE = "+";
    private static final String ID_SEPARATOR = " / ";

    private MetrixNetworkPoint() throws IllegalAccessException {
        throw new IllegalAccessException();
    }

    public static void addTimeSeriesValues(int version, int point, boolean isCurativeMode, String defaultId, List<TimeSeries> timeSeries, Network networkPoint) {
        final boolean isCurativeTimeSeriesAdding = false;

        // Add double time series to network
        List<DoubleTimeSeries> doubleTimeSeries = timeSeries.stream()
                .filter(ts -> ts.getMetadata().getDataType() == TimeSeriesDataType.DOUBLE)
                .map(ts -> (DoubleTimeSeries) ts)
                .toList();
        ReadOnlyTimeSeriesStore store = new ReadOnlyTimeSeriesStoreCache(doubleTimeSeries);
        addTimeSeriesValues(version, point, isCurativeTimeSeriesAdding, defaultId, store, networkPoint);
        if (isCurativeMode) {
            addTimeSeriesValues(version, point, !isCurativeTimeSeriesAdding, defaultId, store, networkPoint);
        }

        // For curative mode, add topology time series to network
        List<StringTimeSeries> stringTimeSeries = timeSeries.stream()
                .filter(ts -> isCurativeMode && ts.getMetadata().getDataType() == TimeSeriesDataType.STRING && ts.getMetadata().getName().compareTo("TOPOLOGY_" + defaultId) == 0)
                .map(ts -> (StringTimeSeries) ts)
                .toList();
        addTopologyTimeSeries(point, stringTimeSeries, networkPoint);
    }

    private static String getPrefix(String prefix, boolean isCurativeMode) {
        return isCurativeMode ? prefix + "CUR_" : prefix;
    }

    private static String getSuffix(boolean isCurativeMode, String defaultId) {
        return isCurativeMode ? "_" + defaultId : "";
    }

    private static void addTimeSeriesValues(int version, int point, boolean isCurativeMode, String defaultId, ReadOnlyTimeSeriesStore store, Network networkPoint) {

        networkPoint.getGenerators().forEach(generator -> addTimeSeriesToGenerator(generator, store, version, point,
                getPrefix("GEN_", isCurativeMode),
                getSuffix(isCurativeMode, defaultId)
        ));

        networkPoint.getLoads().forEach(load -> addTimeSeriesToLoad(load, store, version, point,
                getPrefix("LOAD_", isCurativeMode),
                getSuffix(isCurativeMode, defaultId)
        ));

        networkPoint.getHvdcLines().forEach(hvdcLine -> addTimeSeriesToHvdcLine(hvdcLine, store, version, point,
                getPrefix("HVDC_", isCurativeMode),
                getSuffix(isCurativeMode, defaultId)
        ));

        networkPoint.getTwoWindingsTransformerStream().filter(PhaseTapChangerHolder::hasPhaseTapChanger).forEach(transformer -> addTimeSeriesToPhaseTapChanger(transformer, store, version, point,
                isCurativeMode ? "PST_CUR_TAP_" : "PST_TAP_",
                getSuffix(isCurativeMode, defaultId)
        ));
    }

    private static double getTimeSeriesValue(Identifiable identifiable, ReadOnlyTimeSeriesStore store, int version, int point, String prefix, String suffix) {
        AtomicReference<Double> value = new AtomicReference<>(Double.NaN);
        String timeSeriesName = prefix + identifiable.getId() + suffix;
        if (store.timeSeriesExists(timeSeriesName)) {
            Optional<DoubleTimeSeries> timeSeries = store.getDoubleTimeSeries(timeSeriesName, version);
            timeSeries.ifPresent(timeSeriesList -> {
                double[] values = timeSeriesList.toArray();
                value.set(values[point]);
            });
        }
        return value.get();
    }

    private static void addTimeSeriesToGenerator(Generator generator, ReadOnlyTimeSeriesStore store, int version, int point, String prefix, String suffix) {
        double value = getTimeSeriesValue(generator, store, version, point, prefix, suffix);
        if (!Double.isNaN(value)) {
            generator.setTargetP(generator.getTargetP() + value);
        }
    }

    private static void addTimeSeriesToLoad(Load load, ReadOnlyTimeSeriesStore store, int version, int point, String prefix, String suffix) {
        double value = getTimeSeriesValue(load, store, version, point, prefix, suffix);
        if (!Double.isNaN(value)) {
            load.setP0(load.getP0() - value);
        }
    }

    private static void addTimeSeriesToHvdcLine(HvdcLine hvdcLine, ReadOnlyTimeSeriesStore store, int version, int point, String prefix, String suffix) {
        double value = getTimeSeriesValue(hvdcLine, store, version, point, prefix, suffix);
        if (!Double.isNaN(value)) {
            TimeSeriesMapper.setHvdcLineSetPoint(hvdcLine, value);
        }
    }

    private static void addTimeSeriesToPhaseTapChanger(TwoWindingsTransformer transformer, ReadOnlyTimeSeriesStore store, int version, int point, String prefix, String suffix) {
        double value = getTimeSeriesValue(transformer, store, version, point, prefix, suffix);
        if (!Double.isNaN(value)) {
            transformer.getPhaseTapChanger().setTapPosition((int) value);
        }
    }

    private static void addTopologyTimeSeries(int point, List<StringTimeSeries> stringTimeSeries, Network networkPoint) {
        if (stringTimeSeries.isEmpty()) {
            return;
        }
        StringTimeSeries topologyTimeSeries = stringTimeSeries.get(0);
        String[] values = topologyTimeSeries.toArray();
        String value = values[point];
        String[] ids = value.split(ID_SEPARATOR);
        for (String id : ids) {
            boolean isToOpen = true;
            if (id.startsWith(ID_TO_CLOSE)) {
                isToOpen = false;
                id = id.substring(1);
            }
            Identifiable<?> identifiable = networkPoint.getIdentifiable(id);
            if (identifiable instanceof Branch<?> branch) {
                if (isToOpen) {
                    branch.getTerminal1().disconnect();
                    branch.getTerminal2().disconnect();
                } else {
                    branch.getTerminal1().connect();
                    branch.getTerminal2().connect();
                }
            } else if (identifiable instanceof Switch sw) {
                sw.setOpen(isToOpen);
            }
        }
    }
}