VariantManagerImplTest.java

/**
 * Copyright (c) 2016, All partners of the iTesla project (http://www.itesla-project.eu/consortium)
 * 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.iidm.network.impl;

import com.google.common.collect.Sets;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.extensions.AbstractExtendable;
import com.powsybl.commons.extensions.AbstractExtension;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import gnu.trove.list.array.TDoubleArrayList;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

import static org.junit.jupiter.api.Assertions.*;

/**
 *
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
class VariantManagerImplTest {

    private static final class IdentifiableMock extends AbstractExtendable<IdentifiableMock> implements Identifiable<IdentifiableMock>, MultiVariantObject {

        private final String id;

        private final Set<String> aliases = new HashSet<>();

        private final Set<Integer> extended = new HashSet<>();

        private final Set<Integer> deleted = new HashSet<>();

        private int reducedCount = 0;

        private IdentifiableMock(String id) {
            this.id = id;
        }

        @Override
        public String getId() {
            return id;
        }

        @Override
        public Optional<String> getOptionalName() {
            return Optional.empty();
        }

        @Override
        public String getNameOrId() {
            return id;
        }

        @Override
        public Set<String> getAliases() {
            return Collections.unmodifiableSet(aliases);
        }

        @Override
        public Network getNetwork() {
            return null;
        }

        @Override
        public boolean hasProperty() {
            return false;
        }

        @Override
        public boolean hasProperty(String key) {
            return false;
        }

        @Override
        public String getProperty(String key) {
            return null;
        }

        @Override
        public String getProperty(String key, String defaultValue) {
            return null;
        }

        @Override
        public String setProperty(String key, String value) {
            return null;
        }

        @Override
        public boolean removeProperty(String key) {
            return false;
        }

        @Override
        public Set<String> getPropertyNames() {
            return Collections.emptySet();
        }

        @Override
        public boolean isFictitious() {
            return false;
        }

        @Override
        public void setFictitious(boolean fictitious) {
        }

        @Override
        public void extendVariantArraySize(int initVariantArraySize, int number, int sourceIndex) {
            for (int i = 0; i < number; i++) {
                extended.add(sourceIndex);
            }
        }

        @Override
        public void reduceVariantArraySize(int number) {
            reducedCount += number;
        }

        @Override
        public void deleteVariantArrayElement(int index) {
            deleted.add(index);
        }

        @Override
        public void allocateVariantArrayElement(int[] indexes, int sourceIndex) {
        }

        @Override
        public IdentifiableType getType() {
            return null;
        }
    }

    private static final class LoadExtension extends AbstractExtension<Load> implements MultiVariantObject {

        private final TDoubleArrayList values;

        LoadExtension(Load load, double value) {
            super(load);

            int variantArraySize = getNetwork().getVariantManager().getVariantArraySize();
            this.values = new TDoubleArrayList(variantArraySize);
            for (int i = 0; i < variantArraySize; ++i) {
                this.values.add(value);
            }
        }

        @Override
        public String getName() {
            return "load-extension";
        }

        @Override
        public void extendVariantArraySize(int initVariantArraySize, int number, int sourceIndex) {
            values.ensureCapacity(values.size() + number);
            for (int i = 0; i < number; ++i) {
                values.add(values.get(sourceIndex));
            }
        }

        @Override
        public void reduceVariantArraySize(int number) {
            values.remove(values.size() - number, number);
        }

        @Override
        public void deleteVariantArrayElement(int index) {
            // Nothing to do
        }

        @Override
        public void allocateVariantArrayElement(int[] indexes, int sourceIndex) {
            for (int index : indexes) {
                values.set(index, values.get(sourceIndex));
            }
        }

        double getValue() {
            return this.values.get(getNetwork().getVariantIndex());
        }

        LoadExtension setValue(double value) {
            this.values.set(getNetwork().getVariantIndex(), value);
            return this;
        }

        private NetworkImpl getNetwork() {
            return (NetworkImpl) getExtendable().getNetwork();
        }
    }

    VariantManagerImplTest() {
    }

    @Test
    void test() {
        NetworkImpl network = (NetworkImpl) Network.create("test", "no-format");
        NetworkIndex index = network.getIndex();
        IdentifiableMock identifiable1 = new IdentifiableMock("1");
        index.checkAndAdd(identifiable1);
        VariantManagerImpl variantManager = network.getVariantManager();
        // initial variant test
        assertEquals(1, variantManager.getVariantArraySize());
        assertEquals(Collections.singleton(VariantManagerConstants.INITIAL_VARIANT_ID), variantManager.getVariantIds());
        assertEquals(Collections.singleton(0), variantManager.getVariantIndexes());
        try {
            variantManager.setWorkingVariant("UnknownVariant");
            fail();
        } catch (PowsyblException ignored) {
        }
        try {
            variantManager.removeVariant("UnknownVariant");
            fail();
        } catch (PowsyblException ignored) {
        }
        try {
            variantManager.removeVariant(VariantManagerConstants.INITIAL_VARIANT_ID);
            fail();
        } catch (PowsyblException ignored) {
        }
        // cloning test
        variantManager.cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, "ClonedVariant1");
        assertEquals(2, variantManager.getVariantArraySize());
        assertEquals(Sets.newHashSet(VariantManagerConstants.INITIAL_VARIANT_ID, "ClonedVariant1"), variantManager.getVariantIds());
        assertEquals(Sets.newHashSet(0, 1), variantManager.getVariantIndexes());
        assertEquals(Collections.singleton(0), identifiable1.extended);
        // second cloning test
        variantManager.cloneVariant("ClonedVariant1", "ClonedVariant2");
        assertEquals(3, variantManager.getVariantArraySize());
        assertEquals(Sets.newHashSet(VariantManagerConstants.INITIAL_VARIANT_ID, "ClonedVariant1", "ClonedVariant2"), variantManager.getVariantIds());
        assertEquals(Sets.newHashSet(0, 1, 2), variantManager.getVariantIndexes());
        assertEquals(VariantManagerConstants.INITIAL_VARIANT_ID, variantManager.getWorkingVariantId());
        variantManager.setWorkingVariant("ClonedVariant1");
        assertEquals("ClonedVariant1", variantManager.getWorkingVariantId());
        // clone test with overwriting
        variantManager.cloneVariant("ClonedVariant2", VariantManagerConstants.INITIAL_VARIANT_ID, true);
        assertEquals(3, variantManager.getVariantArraySize());
        assertEquals(Sets.newHashSet(VariantManagerConstants.INITIAL_VARIANT_ID, "ClonedVariant1", "ClonedVariant2"), variantManager.getVariantIds());
        assertEquals(Sets.newHashSet(0, 1, 2), variantManager.getVariantIndexes());
        variantManager.setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID);
        assertEquals(VariantManagerConstants.INITIAL_VARIANT_ID, variantManager.getWorkingVariantId());
        variantManager.setWorkingVariant("ClonedVariant1");
        // "middle" variant removing test
        variantManager.removeVariant("ClonedVariant1");
        try {
            assertEquals(VariantManagerConstants.INITIAL_VARIANT_ID, variantManager.getWorkingVariantId()); // because variant is not set
            fail();
        } catch (Exception ignored) {
        }
        assertEquals(3, variantManager.getVariantArraySize());
        assertEquals(Sets.newHashSet(VariantManagerConstants.INITIAL_VARIANT_ID, "ClonedVariant2"), variantManager.getVariantIds());
        assertEquals(Sets.newHashSet(0, 2), variantManager.getVariantIndexes());
        assertEquals(Collections.singleton(1), identifiable1.deleted);
        // variant array index recycling test
        variantManager.cloneVariant("ClonedVariant2", "ClonedVariant3");
        assertEquals(3, variantManager.getVariantArraySize());
        assertEquals(Sets.newHashSet(VariantManagerConstants.INITIAL_VARIANT_ID, "ClonedVariant2", "ClonedVariant3"), variantManager.getVariantIds());
        assertEquals(Sets.newHashSet(0, 1, 2), variantManager.getVariantIndexes());
        // variant array reduction test
        variantManager.removeVariant("ClonedVariant3");
        assertEquals(3, variantManager.getVariantArraySize());
        variantManager.removeVariant("ClonedVariant2");
        assertEquals(1, variantManager.getVariantArraySize());
        assertEquals(Collections.singleton(VariantManagerConstants.INITIAL_VARIANT_ID), variantManager.getVariantIds());
        assertEquals(Collections.singleton(0), variantManager.getVariantIndexes());
        assertEquals(2, identifiable1.reducedCount);
    }

    @Test
    void testMultipleNetworks() {
        Network network1 = Network.create("network1", "no-format");
        Network network2 = Network.create("network2", "no-format");

        VariantManager variantManager1 = network1.getVariantManager();
        variantManager1.allowVariantMultiThreadAccess(true);
        assertEquals(VariantManagerConstants.INITIAL_VARIANT_ID, variantManager1.getWorkingVariantId());

        VariantManager variantManager2 = network2.getVariantManager();
        variantManager2.allowVariantMultiThreadAccess(true);
        assertEquals(VariantManagerConstants.INITIAL_VARIANT_ID, variantManager1.getWorkingVariantId());

        variantManager1.cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, "testVariant");

        variantManager1.setWorkingVariant("testVariant");
        assertEquals("testVariant", variantManager1.getWorkingVariantId());

        //active variant for variant manager 2 should not have changed
        assertEquals(VariantManagerConstants.INITIAL_VARIANT_ID, variantManager2.getWorkingVariantId());
    }

    @Test
    void testMultiStateExtensions() {
        String variante1 = "v1";
        String variante2 = "v2";

        Network network = EurostagTutorialExample1Factory.create();
        VariantManager manager = network.getVariantManager();

        Load load = network.getLoad("LOAD");
        LoadExtension loadExtension = new LoadExtension(load, 100);
        load.addExtension(LoadExtension.class, loadExtension);

        manager.cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, variante1);
        manager.setWorkingVariant(variante1);

        assertEquals(100.0, loadExtension.getValue(), 0.0);
        loadExtension.setValue(200);
        assertEquals(200.0, loadExtension.getValue(), 0.0);

        manager.setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID);
        assertEquals(100.0, loadExtension.getValue(), 0.0);

        manager.cloneVariant(variante1, variante2);
        manager.setWorkingVariant(variante2);
        assertEquals(200.0, loadExtension.getValue(), 0.0);

        loadExtension.setValue(300);
        manager.removeVariant(variante1);
        assertEquals(300.0, loadExtension.getValue(), 0.0);

        manager.setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID);
        assertEquals(100.0, loadExtension.getValue(), 0.0);

        manager.removeVariant(variante2);
        assertEquals(100.0, loadExtension.getValue(), 0.0);
    }

    @Test
    void overwriteVariant() {
        String variante1 = "v1";

        Network network = EurostagTutorialExample1Factory.create();
        VariantManager manager = network.getVariantManager();

        Generator generator = network.getGenerator("GEN");
        assertEquals(607.0, generator.getTargetP(), 0.0);

        manager.cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, variante1);
        manager.setWorkingVariant(variante1);
        assertEquals(607.0, generator.getTargetP(), 0.0);
        generator.setTargetP(605);
        assertEquals(605.0, generator.getTargetP(), 0.0);
        manager.setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID);
        assertEquals(607.0, generator.getTargetP(), 0.0);
        manager.cloneVariant(variante1, VariantManagerConstants.INITIAL_VARIANT_ID, true);
        manager.setWorkingVariant(VariantManagerConstants.INITIAL_VARIANT_ID);
        assertEquals(605.0, generator.getTargetP(), 0.0);
    }

    @Test
    void testVariantIndexKept() throws Exception {
        NetworkImpl network = (NetworkImpl) Network.create("testVariantIndexKept", "no-format");
        VariantManager variantManager = new VariantManagerImpl(network);
        variantManager.cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, "ClonedVariant1");
        variantManager.setWorkingVariant("ClonedVariant1");
        variantManager.allowVariantMultiThreadAccess(true);
        assertEquals(variantManager.getWorkingVariantId(), "ClonedVariant1");
        CountDownLatch cdl1 = new CountDownLatch(1);
        (new Thread() {
            public void run() {
                try {
                    variantManager.allowVariantMultiThreadAccess(true);
                } finally {
                    cdl1.countDown();
                }
            }
        }).start();
        cdl1.await();
        assertEquals(variantManager.getWorkingVariantId(), "ClonedVariant1");
    }

    @Test
    void testMultipleSetAllowMultiThreadTrue() throws Exception {
        NetworkImpl network = (NetworkImpl) Network.create("testMultipleSetAllowMultiThreadTrue", "no-format");
        VariantManager variantManager = new VariantManagerImpl(network);
        variantManager.allowVariantMultiThreadAccess(true);
        CountDownLatch cdl1 = new CountDownLatch(1);
        Exception[] exceptionThrown = new Exception[1];
        (new Thread() {
            public void run() {
                try {
                    variantManager.allowVariantMultiThreadAccess(true);
                    exceptionThrown[0] = null;
                } catch (Exception e) {
                    exceptionThrown[0] = e;
                } finally {
                    cdl1.countDown();
                }
            }
        }).start();
        cdl1.await();
        if (exceptionThrown[0] != null) {
            fail(exceptionThrown[0]);
        }
    }

    @Test
    void testVariantIndexSwitch() throws Exception {
        NetworkImpl network = (NetworkImpl) Network.create("testVariantIndexSwitch", "no-format");
        VariantManager variantManager = new VariantManagerImpl(network);
        assertEquals(VariantManagerConstants.INITIAL_VARIANT_ID, variantManager.getWorkingVariantId());
        variantManager.cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, "ClonedVariant1");
        variantManager.setWorkingVariant("ClonedVariant1");
        assertEquals("ClonedVariant1", variantManager.getWorkingVariantId());
        variantManager.allowVariantMultiThreadAccess(false);
        assertEquals("ClonedVariant1", variantManager.getWorkingVariantId());
        variantManager.allowVariantMultiThreadAccess(true);
        assertTrue(variantManager.isVariantMultiThreadAccessAllowed());
        assertEquals("ClonedVariant1", variantManager.getWorkingVariantId());
        variantManager.allowVariantMultiThreadAccess(true);
        assertTrue(variantManager.isVariantMultiThreadAccessAllowed());
        assertEquals("ClonedVariant1", variantManager.getWorkingVariantId());
        variantManager.allowVariantMultiThreadAccess(false);
        variantManager.removeVariant("ClonedVariant1");
        variantManager.allowVariantMultiThreadAccess(false);
        variantManager.allowVariantMultiThreadAccess(true);
        assertTrue(variantManager.isVariantMultiThreadAccessAllowed());
        variantManager.allowVariantMultiThreadAccess(false);
        assertEquals(VariantManagerConstants.INITIAL_VARIANT_ID, variantManager.getWorkingVariantId());
        variantManager.cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, "ClonedVariant2");
        variantManager.allowVariantMultiThreadAccess(true);
        assertTrue(variantManager.isVariantMultiThreadAccessAllowed());
        variantManager.removeVariant("ClonedVariant2");
        variantManager.allowVariantMultiThreadAccess(false);
        assertEquals(VariantManagerConstants.INITIAL_VARIANT_ID, variantManager.getWorkingVariantId());
    }
}