LossesCompensationTests.java

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

import com.powsybl.flow_decomposition.xnec_provider.XnecProviderByIds;
import com.powsybl.iidm.network.*;
import com.powsybl.loadflow.LoadFlow;
import com.powsybl.loadflow.LoadFlowParameters;
import org.junit.jupiter.api.Test;

import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.powsybl.flow_decomposition.TestUtils.getLossOnBus;
import static com.powsybl.flow_decomposition.TestUtils.importNetwork;
import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Sebastien Murgey {@literal <sebastien.murgey at rte-france.com>}
 * @author Hugo Schindler {@literal <hugo.schindler at rte-france.com>}
 */
class LossesCompensationTests {
    private static final double EPSILON = 1e-3;
    private static final boolean AC_LOAD_FLOW = false;

    @Test
    void checkThatLossesCompensationIsDoneCorrectly() {
        String networkFileName = "NETWORK_SINGLE_LOAD_TWO_GENERATORS_WITH_COUNTRIES.uct";

        Network network = importNetwork(networkFileName);
        LoadFlowParameters loadFlowParameters = new LoadFlowParameters();
        loadFlowParameters.setDc(AC_LOAD_FLOW);
        LoadFlow.run(network, loadFlowParameters);
        LossesCompensator lossesCompensator = new LossesCompensator(FlowDecompositionParameters.DISABLE_LOSSES_COMPENSATION_EPSILON);
        lossesCompensator.run(network);

        assessSingleLoadTwoGeneratorsNetworkLossesCompensation(network);
    }

    @Test
    void checkThatLossesCompensationDoesEnforceAcLoadflow() {
        String networkFileName = "NETWORK_SINGLE_LOAD_TWO_GENERATORS_WITH_COUNTRIES.uct";
        Network network = importNetwork(networkFileName);
        LoadFlowParameters loadFlowParameters = new LoadFlowParameters();
        loadFlowParameters.setDc(AC_LOAD_FLOW);
        LoadFlow.run(network, loadFlowParameters);
        LossesCompensator lossesCompensator = new LossesCompensator(FlowDecompositionParameters.DISABLE_LOSSES_COMPENSATION_EPSILON);
        lossesCompensator.run(network);

        assessSingleLoadTwoGeneratorsNetworkLossesCompensation(network);
    }

    private void assessSingleLoadTwoGeneratorsNetworkLossesCompensation(Network network) {
        Load lossesFgen = network.getLoad("LOSSES FGEN1 11");
        assertNotNull(lossesFgen);
        assertEquals("FGEN1 1", lossesFgen.getTerminal().getVoltageLevel().getId());
        assertEquals(0.0625, lossesFgen.getP0(), EPSILON);
        Load lossesBgen = network.getLoad("LOSSES BGEN2 11");
        assertNotNull(lossesBgen);
        assertEquals("BGEN2 1", lossesBgen.getTerminal().getVoltageLevel().getId());
        assertEquals(0.0625, lossesBgen.getP0(), EPSILON);
        Load lossesBload = network.getLoad("LOSSES BLOAD 11");
        assertNotNull(lossesBload);
        assertEquals("BLOAD 1", lossesBload.getTerminal().getVoltageLevel().getId());
        assertEquals(0.0, lossesBload.getP0(), EPSILON);
    }

    @Test
    void checkThatLossesCompensationOnCentralGeneratorDoesAggregateLosses() {
        String networkFileName = "NETWORK_TWO_LOADS_SINGLE_GENERATOR_WITH_COUNTRIES.uct";

        Network network = importNetwork(networkFileName);
        LoadFlowParameters loadFlowParameters = new LoadFlowParameters();
        loadFlowParameters.setDc(AC_LOAD_FLOW);
        LoadFlow.run(network, loadFlowParameters);
        LossesCompensator lossesCompensator = new LossesCompensator(FlowDecompositionParameters.DISABLE_LOSSES_COMPENSATION_EPSILON);
        lossesCompensator.run(network);

        Load lossesFgenX = network.getLoad("LOSSES FGEN1 11");
        assertNotNull(lossesFgenX);
        assertEquals("FGEN1 1", lossesFgenX.getTerminal().getVoltageLevel().getId());
        assertEquals(0.0625 * 4, lossesFgenX.getP0(), EPSILON);
        Load lossesBloadX = network.getLoad("LOSSES BLOAD111");
        assertNotNull(lossesBloadX);
        assertEquals("BLOAD11", lossesBloadX.getTerminal().getVoltageLevel().getId());
        assertEquals(0.0, lossesBloadX.getP0(), EPSILON);
    }

    @Test
    void checkThatLossesCompensationOnTieLineDoesDispatchLossesProportionallyToEachSideResistance() {
        String networkFileName = "NETWORK_SINGLE_LOAD_TWO_GENERATORS_WITH_XNODE.uct";

        Network network = importNetwork(networkFileName);
        LoadFlowParameters loadFlowParameters = new LoadFlowParameters();
        loadFlowParameters.setDc(AC_LOAD_FLOW);
        LoadFlow.run(network, loadFlowParameters);
        LossesCompensator lossesCompensator = new LossesCompensator(FlowDecompositionParameters.DISABLE_LOSSES_COMPENSATION_EPSILON);
        lossesCompensator.run(network);

        TieLine tieLine = network.getTieLine("FGEN1 11 X     11 1 + X     11 BLOAD 11 1");
        assertEquals(0.0, tieLine.getDanglingLine1().getB(), EPSILON);
        assertEquals(0.0, tieLine.getDanglingLine2().getB(), EPSILON);

        Load lossesFgenBload = network.getLoad("LOSSES FGEN1 11 X     11 1 + X     11 BLOAD 11 1");
        assertNull(lossesFgenBload);

        Load lossesFgenX = network.getLoad("LOSSES FGEN1 11");
        assertNotNull(lossesFgenX);
        assertEquals("FGEN1 1", lossesFgenX.getTerminal().getVoltageLevel().getId());
        assertEquals(0.015625, lossesFgenX.getP0(), EPSILON);
        Load lossesBloadX = network.getLoad("LOSSES BLOAD 11");
        assertNotNull(lossesBloadX);
        assertEquals("BLOAD 1", lossesBloadX.getTerminal().getVoltageLevel().getId());
        assertEquals(0.046875, lossesBloadX.getP0(), EPSILON);
    }

    @Test
    void checkThatLossesCompensationOnTieLineDoesDispatchLossesProportionallyToEachSideResistanceWithB() {
        String networkFileName = "NETWORK_SINGLE_LOAD_TWO_GENERATORS_WITH_XNODE.uct";

        Network network = importNetwork(networkFileName);
        TieLine tieLine = network.getTieLine("FGEN1 11 X     11 1 + X     11 BLOAD 11 1");
        tieLine.getDanglingLine1().setB(1E-3);
        tieLine.getDanglingLine2().setB(1E-3);
        LoadFlowParameters loadFlowParameters = new LoadFlowParameters();
        loadFlowParameters.setDc(AC_LOAD_FLOW);
        LoadFlow.run(network, loadFlowParameters);
        LossesCompensator lossesCompensator = new LossesCompensator(FlowDecompositionParameters.DISABLE_LOSSES_COMPENSATION_EPSILON);
        lossesCompensator.run(network);

        Load lossesFgenBload = network.getLoad("LOSSES FGEN1 11 X     11 1 + X     11 BLOAD 11 1");
        assertNull(lossesFgenBload);

        Load lossesFgenX = network.getLoad("LOSSES FGEN1 11");
        assertNotNull(lossesFgenX);
        assertEquals("FGEN1 1", lossesFgenX.getTerminal().getVoltageLevel().getId());
        assertEquals(0.020389, lossesFgenX.getP0(), EPSILON);
        Load lossesBloadX = network.getLoad("LOSSES BLOAD 11");
        assertNotNull(lossesBloadX);
        assertEquals("BLOAD 1", lossesBloadX.getTerminal().getVoltageLevel().getId());
        assertEquals(0.061168, lossesBloadX.getP0(), EPSILON);
    }

    @Test
    void checkThatDefaultFlowDecompositionDoesNotCompensateLosses() {
        String networkFileName = "NETWORK_SINGLE_LOAD_TWO_GENERATORS_WITH_COUNTRIES_EXTRA_SUBSTATION.uct";
        Network network = importNetwork(networkFileName);
        String xnecId = "FLOAD 11 BLOAD 11 1";

        FlowDecompositionComputer flowDecompositionComputer = new FlowDecompositionComputer();
        XnecProvider xnecProvider = XnecProviderByIds.builder().addNetworkElementsOnBasecase(Set.of(xnecId)).build();
        FlowDecompositionResults flowDecompositionResults = flowDecompositionComputer.run(xnecProvider, network);

        assertEquals(99.813, flowDecompositionResults.getDecomposedFlowMap().get(xnecId).getAllocatedFlow(), EPSILON);
        assertEquals(0.0936, flowDecompositionResults.getDecomposedFlowMap().get(xnecId).getLoopFlow(Country.FR), EPSILON);
        assertEquals(0.0936, flowDecompositionResults.getDecomposedFlowMap().get(xnecId).getLoopFlow(Country.BE), EPSILON);
    }

    @Test
    void checkThatEnablingLossesCompensationDoesImpactFlowDecompositionCorrectly() {
        String networkFileName = "NETWORK_SINGLE_LOAD_TWO_GENERATORS_WITH_COUNTRIES_EXTRA_SUBSTATION.uct";
        Network network = importNetwork(networkFileName);
        String xnecId = "FLOAD 11 BLOAD 11 1";

        FlowDecompositionParameters flowDecompositionParameters = new FlowDecompositionParameters();
        flowDecompositionParameters.setEnableLossesCompensation(FlowDecompositionParameters.ENABLE_LOSSES_COMPENSATION);
        FlowDecompositionComputer flowDecompositionComputer = new FlowDecompositionComputer(flowDecompositionParameters);
        XnecProvider xnecProvider = XnecProviderByIds.builder().addNetworkElementsOnBasecase(Set.of(xnecId)).build();
        FlowDecompositionResults flowDecompositionResults = flowDecompositionComputer.run(xnecProvider, network);

        assertEquals(99.813, flowDecompositionResults.getDecomposedFlowMap().get(xnecId).getAllocatedFlow(), EPSILON);
        assertEquals(-0.609, flowDecompositionResults.getDecomposedFlowMap().get(xnecId).getLoopFlow(Country.FR), EPSILON);
        assertEquals(-0.609, flowDecompositionResults.getDecomposedFlowMap().get(xnecId).getLoopFlow(Country.BE), EPSILON);
    }

    @Test
    void checkThatDisablingLossesCompensationDoesImpactFlowDecompositionCorrectly() {
        String networkFileName = "NETWORK_SINGLE_LOAD_TWO_GENERATORS_WITH_COUNTRIES_EXTRA_SUBSTATION.uct";
        Network network = importNetwork(networkFileName);
        String xnecId = "FLOAD 11 BLOAD 11 1";

        FlowDecompositionParameters flowDecompositionParameters = new FlowDecompositionParameters();
        flowDecompositionParameters.setEnableLossesCompensation(FlowDecompositionParameters.DISABLE_LOSSES_COMPENSATION);
        FlowDecompositionComputer flowDecompositionComputer = new FlowDecompositionComputer(flowDecompositionParameters);
        XnecProvider xnecProvider = XnecProviderByIds.builder().addNetworkElementsOnBasecase(Set.of(xnecId)).build();
        FlowDecompositionResults flowDecompositionResults = flowDecompositionComputer.run(xnecProvider, network);

        assertEquals(99.813, flowDecompositionResults.getDecomposedFlowMap().get(xnecId).getAllocatedFlow(), EPSILON);
        assertEquals(0.0936, flowDecompositionResults.getDecomposedFlowMap().get(xnecId).getLoopFlow(Country.FR), EPSILON);
        assertEquals(0.0936, flowDecompositionResults.getDecomposedFlowMap().get(xnecId).getLoopFlow(Country.BE), EPSILON);
    }

    @Test
    void testLossCompensationCanBeAppliedToNetworkWithVariants() {
        LoadFlowParameters loadFlowParameters = new LoadFlowParameters();
        loadFlowParameters.setDc(AC_LOAD_FLOW);

        LossesCompensator lossesCompensator = new LossesCompensator(FlowDecompositionParameters.DISABLE_LOSSES_COMPENSATION_EPSILON);

        String networkFileName = "NETWORK_SINGLE_LOAD_TWO_GENERATORS_WITH_COUNTRIES.uct";
        Network network = importNetwork(networkFileName);
        network.getVariantManager().cloneVariant("InitialState", "NewState");
        int networkHash = network.hashCode();

        LoadFlow.run(network, loadFlowParameters);
        assertEquals(networkHash, network.hashCode());

        Map<Bus, Double> busToLossMap = network.getBusBreakerView().getBusStream()
            .collect(Collectors.toMap(Function.identity(), bus -> getLossOnBus(network, bus)
            ));

        lossesCompensator.run(network);
        assertEquals(networkHash, network.hashCode());

        busToLossMap.forEach((bus, losses) -> {
            Load load = network.getLoad(LossesCompensator.getLossesId(bus.getId()));
            assertNotNull(load);
            assertEquals(losses, load.getP0());
        });

        network.getVariantManager().setWorkingVariant("NewState");
        assertEquals(networkHash, network.hashCode());

        // On the other variant, the loss loads are still zero MW
        busToLossMap.keySet().forEach(bus -> {
            Load load = network.getLoad(LossesCompensator.getLossesId(bus.getId()));
            assertNotNull(load);
            assertEquals(0.0, load.getP0());
        });

        LoadFlow.run(network, loadFlowParameters);
        assertEquals(networkHash, network.hashCode());

        lossesCompensator.run(network);
        assertEquals(networkHash, network.hashCode());

        // On the other variant, the loss loads are not zero MW anymore
        busToLossMap.forEach((bus, losses) -> {
            Load load = network.getLoad(LossesCompensator.getLossesId(bus.getId()));
            assertEquals(losses, load.getP0());
        });

        network.getVariantManager().setWorkingVariant("InitialState");
        assertEquals(networkHash, network.hashCode());

        //But the loss loads have not been reset on the initial variant
        busToLossMap.forEach((bus, losses) -> {
            Load load = network.getLoad(LossesCompensator.getLossesId(bus.getId()));
            assertEquals(losses, load.getP0());
        });
    }

    @Test
    void testLossCompensationComputerCanBeAppliedToMultipleNetworks() {
        LoadFlowParameters loadFlowParameters = new LoadFlowParameters();
        loadFlowParameters.setDc(AC_LOAD_FLOW);

        LossesCompensator lossesCompensator = new LossesCompensator(FlowDecompositionParameters.DISABLE_LOSSES_COMPENSATION_EPSILON);

        String networkFileName1 = "NETWORK_SINGLE_LOAD_TWO_GENERATORS_WITH_COUNTRIES.uct";
        String networkFileName2 = "NETWORK_SINGLE_LOAD_TWO_GENERATORS_WITH_COUNTRIES_INVERTED.uct";

        Network network1 = importNetwork(networkFileName1);
        Network network2 = importNetwork(networkFileName2);

        int hashCode1 = network1.hashCode();
        int hashCode2 = network2.hashCode();
        assertNotEquals(hashCode1, hashCode2);

        LoadFlow.run(network1, loadFlowParameters);
        LoadFlow.run(network2, loadFlowParameters);

        assertEquals(hashCode1, network1.hashCode());
        assertEquals(hashCode2, network2.hashCode());

        Map<Bus, Double> busToLossMap1 = network1.getBusBreakerView().getBusStream()
            .collect(Collectors.toMap(Function.identity(), bus -> getLossOnBus(network1, bus)
            ));
        Map<Bus, Double> busToLossMap2 = network2.getBusBreakerView().getBusStream()
            .collect(Collectors.toMap(Function.identity(), bus -> getLossOnBus(network2, bus)
            ));

        lossesCompensator.run(network1);
        lossesCompensator.run(network2);

        assertEquals(hashCode1, network1.hashCode());
        assertEquals(hashCode2, network2.hashCode());

        busToLossMap1.forEach((bus, losses) -> {
            Load load = network1.getLoad(LossesCompensator.getLossesId(bus.getId()));
            assertNotNull(load);
            assertEquals(losses, load.getP0());
        });
        busToLossMap2.forEach((bus, losses) -> {
            Load load = network2.getLoad(LossesCompensator.getLossesId(bus.getId()));
            assertNotNull(load);
            assertEquals(losses, load.getP0());
        });
    }

    /*
        Test flow decomposition with losses compensation in a simple network
          FR : bus1
          BE : bus2, bus3
                                   ____ bus3
                                  |      |
                                 l32     d3 (1)
                                  |
                bus1 --- l21 --- bus2
                 |                |
                g1 (3)           d2 (2)

         Line l32 is open at side 1 (bus 3)
         Line characteristics: (r,x,g,b) = (0.01, 0.1, 0.0, 0.5)
     */
    @Test
    void testLossCompensationWithLineConnectedToOnlyOneSide() {
        String networkFileName = "lossesCompensatorLineConnectedToOneSide.xiidm";
        Network network = importNetwork(networkFileName);
        assertEquals(2, network.getLoadStream().count());
        LoadFlow.run(network, new LoadFlowParameters().setDc(AC_LOAD_FLOW));
        LossesCompensator lossesCompensator = new LossesCompensator(FlowDecompositionParameters.DEFAULT_LOSSES_COMPENSATION_EPSILON);
        lossesCompensator.run(network);
        assertEquals(5, network.getLoadStream().count());
        Load lossesb1 = network.getLoad("LOSSES b1");
        assertNotNull(lossesb1);
        assertEquals("b1_vl", lossesb1.getTerminal().getVoltageLevel().getId());
        assertEquals(0.061171, lossesb1.getP0(), 1e-6);
        assertTrue(lossesb1.isFictitious());
        Load lossesb2 = network.getLoad("LOSSES b2");
        assertNotNull(lossesb2);
        assertEquals("b2_vl", lossesb2.getTerminal().getVoltageLevel().getId());
        assertEquals(0.003581, lossesb2.getP0(), 1e-6);
        assertTrue(lossesb2.isFictitious());
        Load lossesb3 = network.getLoad("LOSSES b3");
        assertNotNull(lossesb3);
        assertEquals("b3_vl", lossesb3.getTerminal().getVoltageLevel().getId());
        assertEquals(0.0, lossesb3.getP0(), 1e-6);
        assertTrue(lossesb3.isFictitious());
    }
}