GlskPointLinearGlskConverter.java

/*
 * Copyright (c) 2020, 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.glsk.api.util.converters;

import com.powsybl.glsk.api.GlskPoint;
import com.powsybl.glsk.api.GlskRegisteredResource;
import com.powsybl.glsk.api.GlskShiftKey;
import com.powsybl.glsk.commons.CountryEICode;
import com.powsybl.glsk.commons.GlskException;
import com.powsybl.iidm.network.*;
import com.powsybl.sensitivity.SensitivityVariableSet;
import com.powsybl.sensitivity.WeightedSensitivityVariable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Convert a single GlskPoint to LinearGlsk
 * @author Pengbo Wang {@literal <pengbo.wang@rte-international.com>}
 * @author Peter Mitri {@literal <peter.mitri@rte-france.com>}
 */
public final class GlskPointLinearGlskConverter {
    private static final Logger LOGGER = LoggerFactory.getLogger(GlskPointLinearGlskConverter.class);

    private GlskPointLinearGlskConverter() {
        throw new AssertionError("Utility class should not be instantiated");
    }

    /**
     * @param network IIDM network
     * @param glskPoint GLSK Point
     * @return farao-core LinearGlsk
     */
    public static SensitivityVariableSet convert(Network network, GlskPoint glskPoint) {

        String sensitivityVariableSetId = glskPoint.getSubjectDomainmRID() + ":" + glskPoint.getPointInterval().toString();

        /* Linear GLSK is used as sensitivityVariable in FlowBasedComputation
         * When it is added into sensivitityFactors, we should be able to find out LinearGlsk's country or NetWorkArea
         * For the moment, LinearGlsk's name is used to trace LinearGlsk's country or NetworkArea.
         * We could also added another attribute in LinearGlsk to mark this information,
         * but this change need to be in Powsybl-core
         */

        Objects.requireNonNull(glskPoint.getGlskShiftKeys());

        List<WeightedSensitivityVariable> weightedSensitivityVariables = new ArrayList<>();

        if (glskPoint.getGlskShiftKeys().size() > 2) {
            throw new GlskException("Multi (GSK+LSK) shift keys not supported yet...");
        }

        for (GlskShiftKey glskShiftKey : glskPoint.getGlskShiftKeys()) {
            if (glskShiftKey.getBusinessType().equals("B42")) {
                if (glskShiftKey.getRegisteredResourceArrayList().isEmpty()) {
                    LOGGER.debug("GLSK Type B42, empty registered resources list --> country (proportional) GLSK");
                    convertCountryProportional(network, glskShiftKey, weightedSensitivityVariables);
                } else {
                    LOGGER.debug("GLSK Type B42, not empty registered resources list --> (explicit/manual) proportional GSK");
                    convertExplicitProportional(network, glskShiftKey, weightedSensitivityVariables);
                }
            } else if (glskShiftKey.getBusinessType().equals("B43")) {
                LOGGER.debug("GLSK Type B43 --> participation factor proportional GSK");
                if (glskShiftKey.getRegisteredResourceArrayList().isEmpty()) {
                    throw new GlskException("Empty Registered Resources List in B43 type shift key.");
                } else {
                    convertParticipationFactor(network, glskShiftKey, weightedSensitivityVariables);
                }
            } else {
                throw new GlskException("convert not supported");
            }
        }

        return new SensitivityVariableSet(sensitivityVariableSetId, weightedSensitivityVariables);
    }

    /**
     * @param network iidm network
     * @param glskShiftKey country type shiftkey
     * @param weightedSensitivityVariables linearGlsk to be filled
     */
    private static void convertCountryProportional(Network network, GlskShiftKey glskShiftKey, List<WeightedSensitivityVariable> weightedSensitivityVariables) {
        Country country = new CountryEICode(glskShiftKey.getSubjectDomainmRID()).getCountry();
        //Generator A04 or Load A05
        if (glskShiftKey.getPsrType().equals("A04")) {
            //Generator A04
            List<Generator> generators = network.getGeneratorStream()
                    .filter(generator -> country.equals(generator.getTerminal().getVoltageLevel().getSubstation().map(Substation::getNullableCountry).orElse(null)))
                    .filter(NetworkUtil::isCorrect)
                    .collect(Collectors.toList());
            //calculate sum P of country's generators
            double totalCountryP = generators.stream().mapToDouble(NetworkUtil::pseudoTargetP).sum();
            //calculate factor of each generator
            generators.forEach(generator -> weightedSensitivityVariables.add(new WeightedSensitivityVariable(generator.getId(),
                    glskShiftKey.getQuantity().floatValue() * (float) NetworkUtil.pseudoTargetP(generator) / (float) totalCountryP)));
        } else if (glskShiftKey.getPsrType().equals("A05")) {
            //Load A05
            List<Load> loads = network.getLoadStream()
                    .filter(load -> country.equals(load.getTerminal().getVoltageLevel().getSubstation().map(Substation::getNullableCountry).orElse(null)))
                    .filter(NetworkUtil::isCorrect)
                    .collect(Collectors.toList());
            double totalCountryLoad = loads.stream().mapToDouble(NetworkUtil::pseudoP0).sum();
            loads.forEach(load -> weightedSensitivityVariables.add(new WeightedSensitivityVariable(load.getId(),
                    glskShiftKey.getQuantity().floatValue() * (float) NetworkUtil.pseudoP0(load) / (float) totalCountryLoad)));
        } else {
            //unknown PsrType
            throw new GlskException("convertCountryProportional PsrType not supported");
        }
    }

    /**
     * @param network iidm network
     * @param glskShiftKey explicit type shiftkey
     * @param weightedSensitivityVariables linearGlsk to be filled
     */
    private static void convertExplicitProportional(Network network, GlskShiftKey glskShiftKey, List<WeightedSensitivityVariable> weightedSensitivityVariables) {
        List<DanglingLine> danglingLines = glskShiftKey.getRegisteredResourceArrayList().stream()
            .map(rr -> rr.getDanglingLineId(network))
            .filter(Objects::nonNull)
            .map(network::getDanglingLine)
            .filter(NetworkUtil::isCorrect)
            .collect(Collectors.toList());
        double totalP = danglingLines.stream().mapToDouble(NetworkUtil::pseudoP0).sum();
        //Generator A04 or Load A05
        if (glskShiftKey.getPsrType().equals("A04")) {
            //Generator A04
            List<Generator> generators = glskShiftKey.getRegisteredResourceArrayList().stream()
                    .map(GlskRegisteredResource::getGeneratorId)
                    .map(network::getGenerator)
                    .filter(NetworkUtil::isCorrect)
                    .collect(Collectors.toList());
            totalP += generators.stream().mapToDouble(NetworkUtil::pseudoTargetP).sum();
            for (Generator generator : generators) {
                weightedSensitivityVariables.add(new WeightedSensitivityVariable(generator.getId(),
                    glskShiftKey.getQuantity().floatValue() * (float) NetworkUtil.pseudoTargetP(generator) / (float) totalP));
            }
        } else if (glskShiftKey.getPsrType().equals("A05")) {
            //Load A05
            List<Load> loads = glskShiftKey.getRegisteredResourceArrayList().stream()
                    .map(GlskRegisteredResource::getLoadId)
                    .map(network::getLoad)
                    .filter(NetworkUtil::isCorrect)
                    .collect(Collectors.toList());
            totalP += loads.stream().mapToDouble(NetworkUtil::pseudoP0).sum();
            for (Load load : loads) {
                weightedSensitivityVariables.add(new WeightedSensitivityVariable(load.getId(),
                    glskShiftKey.getQuantity().floatValue() * (float) NetworkUtil.pseudoP0(load) / (float) totalP));
            }
        } else {
            //unknown PsrType
            throw new GlskException("convertExplicitProportional PsrType not supported");
        }
        for (DanglingLine danglingLine : danglingLines) {
            weightedSensitivityVariables.add(new WeightedSensitivityVariable(danglingLine.getId(),
                glskShiftKey.getQuantity().floatValue() * (float) NetworkUtil.pseudoP0(danglingLine) / (float) totalP));
        }
    }

    /**
     * @param network iidm network
     * @param glskShiftKey parcitipation factor type shiftkey
     * @param weightedSensitivityVariables linearGlsk to be filled
     */
    private static void convertParticipationFactor(Network network, GlskShiftKey glskShiftKey, List<WeightedSensitivityVariable> weightedSensitivityVariables) {
        //Generator A04 or Load A05
        List<GlskRegisteredResource> danglingLineResources = glskShiftKey.getRegisteredResourceArrayList().stream()
            .filter(danglingLineResource -> danglingLineResource.getDanglingLineId(network) != null &&
                NetworkUtil.isCorrect(network.getDanglingLine(danglingLineResource.getDanglingLineId(network))))
            .collect(Collectors.toList());
        double totalFactor = danglingLineResources.stream().mapToDouble(GlskRegisteredResource::getParticipationFactor).sum();
        if (glskShiftKey.getPsrType().equals("A04")) {
            //Generator A04
            List<GlskRegisteredResource> generatorResources = glskShiftKey.getRegisteredResourceArrayList().stream()
                .filter(generatorResource -> NetworkUtil.isCorrect(network.getGenerator(generatorResource.getGeneratorId())))
                .collect(Collectors.toList());
            totalFactor += generatorResources.stream().mapToDouble(GlskRegisteredResource::getParticipationFactor).sum();
            if (totalFactor < 1e-10) {
                throw new GlskException("total factor is zero");
            }
            for (GlskRegisteredResource generatorResource : generatorResources) {
                weightedSensitivityVariables.add(new WeightedSensitivityVariable(generatorResource.getGeneratorId(),
                    glskShiftKey.getQuantity().floatValue() * (float) generatorResource.getParticipationFactor() / (float) totalFactor));
            }
        } else if (glskShiftKey.getPsrType().equals("A05")) {
            //Load A05
            List<GlskRegisteredResource> loadResources = glskShiftKey.getRegisteredResourceArrayList().stream()
                .filter(loadResource -> NetworkUtil.isCorrect(network.getLoad(loadResource.getLoadId())))
                .collect(Collectors.toList());
            totalFactor += loadResources.stream().mapToDouble(GlskRegisteredResource::getParticipationFactor).sum();
            if (totalFactor < 1e-10) {
                throw new GlskException("total factor is zero");
            }
            for (GlskRegisteredResource loadResource : loadResources) {
                weightedSensitivityVariables.add(new WeightedSensitivityVariable(loadResource.getLoadId(),
                    glskShiftKey.getQuantity().floatValue() * (float) loadResource.getParticipationFactor() / (float) totalFactor));
            }
        } else {
            //unknown PsrType
            throw new GlskException("convertParticipationFactor PsrType not supported");
        }
        for (GlskRegisteredResource danglingLineResource : danglingLineResources) {
            weightedSensitivityVariables.add(new WeightedSensitivityVariable(danglingLineResource.getDanglingLineId(network),
                glskShiftKey.getQuantity().floatValue() * (float) danglingLineResource.getParticipationFactor() / (float) totalFactor));
        }
    }
}