LayoutWithInitialPositionsTest.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/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.nad.layout;

import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.VoltageLevel;
import com.powsybl.nad.AbstractTest;
import com.powsybl.nad.NadParameters;
import com.powsybl.nad.NetworkAreaDiagram;
import com.powsybl.nad.build.iidm.VoltageLevelFilter;
import com.powsybl.nad.model.Graph;
import com.powsybl.nad.model.Point;
import com.powsybl.nad.svg.LabelProvider;
import com.powsybl.nad.svg.StyleProvider;
import com.powsybl.nad.svg.SvgParameters;
import com.powsybl.nad.svg.iidm.DefaultLabelProvider;
import com.powsybl.nad.svg.iidm.NominalVoltageStyleProvider;
import com.powsybl.nad.svg.iidm.StyleProviderFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.StringWriter;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;

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

/**
 * @author Luma Zamarreno {@literal <zamarrenolm at aia.es>}
 */
class LayoutWithInitialPositionsTest extends AbstractTest {

    @BeforeEach
    void setup() {
        setLayoutParameters(new LayoutParameters());
        setSvgParameters(new SvgParameters()
                .setInsertNameDesc(true)
                .setSvgWidthAndHeightAdded(true)
                .setFixedWidth(800));
    }

    @Override
    protected StyleProvider getStyleProvider(Network network) {
        return new NominalVoltageStyleProvider(network);
    }

    private StyleProviderFactory getStyleProviderFactory() {
        return this::getStyleProvider;
    }

    @Override
    protected LabelProvider getLabelProvider(Network network) {
        return new DefaultLabelProvider(network, getSvgParameters());
    }

    @Test
    void testDiamond() {
        checkLayoutWithInitialPositions(LayoutNetworkFactory.createDiamond());
    }

    @Test
    void testNbVoltageLevels() {
        Network network = LayoutNetworkFactory.createDiamond();

        assertEquals(1, VoltageLevelFilter.createVoltageLevelDepthFilter(network, "A 400", 0).getNbVoltageLevels());
        assertEquals(4, VoltageLevelFilter.createVoltageLevelDepthFilter(network, "A 400", 3).getNbVoltageLevels());
        assertEquals(7, VoltageLevelFilter.createVoltageLevelDepthFilter(network, "A 400", 5).getNbVoltageLevels());
        assertEquals(1, VoltageLevelFilter.createVoltageLevelDepthFilter(network, "A 400", 0).getNbVoltageLevels());
    }

    private void checkLayoutWithInitialPositions(Network network) {
        Predicate<VoltageLevel> filter = vl -> vl.getNominalV() >= 100;

        // Perform an initial layout with only a few voltage levels of the network
        Map<String, Point> initialPositions = layoutResult(network, filter);

        // Check initial points contains an entry for all voltage levels filtered
        network.getVoltageLevelStream().filter(filter).forEach(vl -> assertTrue(initialPositions.containsKey(vl.getId())));
        // Check we have voltage levels in the network that are not filtered and thus will not have an initial positions
        assertTrue(network.getVoltageLevelStream().anyMatch(filter.negate()));
        network.getVoltageLevelStream().filter(filter.negate()).forEach(vl -> assertFalse(initialPositions.containsKey(vl.getId())));

        checkAllInitialPositionsFixed(network, initialPositions);
        checkOnlySomeInitialPositionsFixed(network, initialPositions);
    }

    private void checkAllInitialPositionsFixed(Network network, Map<String, Point> initialPositions) {
        // Perform a global layout with all the voltage levels in the network,
        // giving fixed positions for some equipment
        Map<String, Point> allPositions = layoutResult(network, initialPositions);

        // Check positions of initial layout have been preserved in global layout
        for (Map.Entry<String, Point> l : initialPositions.entrySet()) {
            String equipmentId = l.getKey();
            Point expected = l.getValue();
            Point actual = allPositions.get(equipmentId);
            assertNotNull(actual);
            assertEquals(expected.getX(), actual.getX(), 0);
            assertEquals(expected.getY(), actual.getY(), 0);
        }
    }

    private void checkOnlySomeInitialPositionsFixed(Network network, Map<String, Point> initialPositions) {
        // Perform a global layout with all the voltage levels in the network,
        // giving initial positions for some equipment,
        // and fixing the position for only some equipment
        // Only consider fixed the first one in the initial layout
        Set<String> fixedNodes = Set.of(initialPositions.keySet().iterator().next());
        Map<String, Point> allPositions = layoutResult(network, initialPositions, fixedNodes);

        // Check positions of initial layout have been preserved in global layout
        for (Map.Entry<String, Point> l : initialPositions.entrySet()) {
            String equipmentId = l.getKey();
            Point expected = l.getValue();
            Point actual = allPositions.get(equipmentId);
            assertNotNull(actual);
            if (fixedNodes.contains(equipmentId)) {
                assertEquals(expected.getX(), actual.getX(), 0);
                assertEquals(expected.getY(), actual.getY(), 0);
            } else {
                // We expect that the nodes with initial position but that have not been fixed have been moved
                assertTrue(expected.getX() != actual.getX() || expected.getY() != actual.getY());
            }
        }
    }

    private Map<String, Point> layoutResult(Network network, Predicate<VoltageLevel> voltageLevelFilter) {
        return layoutResult(network, Collections.emptyMap(), Collections.emptySet(), Collections.emptyMap(), voltageLevelFilter);
    }

    private Map<String, Point> layoutResult(Network network, Map<String, Point> initialNodePositions, Set<String> nodesWithFixedPositions) {
        return layoutResult(network, initialNodePositions, nodesWithFixedPositions, Collections.emptyMap(), VoltageLevelFilter.NO_FILTER);
    }

    private Map<String, Point> layoutResult(Network network, Map<String, Point> fixedNodePositions) {
        return layoutResult(network, Collections.emptyMap(), Collections.emptySet(), fixedNodePositions, VoltageLevelFilter.NO_FILTER);
    }

    private Map<String, Point> layoutResult(Network network,
                                            Map<String, Point> initialNodePositions,
                                            Set<String> nodesWithFixedPositions,
                                            Map<String, Point> fixedNodePositions,
                                            Predicate<VoltageLevel> voltageLevelFilter
    ) {
        LayoutFactory delegateLayoutFactory = new BasicForceLayoutFactory();
        PositionsLayoutFactory positionsLayoutFactory = new PositionsLayoutFactory(
                delegateLayoutFactory,
                initialNodePositions,
                nodesWithFixedPositions,
                fixedNodePositions);
        StringWriter writer = new StringWriter();
        StringWriter metadataWriter = new StringWriter();
        NetworkAreaDiagram.draw(network, writer, metadataWriter,
                new NadParameters()
                        .setSvgParameters(getSvgParameters())
                        .setLayoutParameters(getLayoutParameters())
                        .setStyleProviderFactory(this::getStyleProvider)
                        .setLayoutFactory(positionsLayoutFactory),
                voltageLevelFilter);
        return positionsLayoutFactory.getLayoutResult().positions;
    }

    static class PositionsLayoutFactory implements LayoutFactory {
        static class LayoutResult {
            Map<String, Point> positions;
        }

        private final LayoutFactory delegateLayoutFactory;
        private final LayoutResult layoutResult = new LayoutResult();
        private final Map<String, Point> initialNodePositions;
        private final Set<String> nodesWithFixedPositions;
        private final Map<String, Point> fixedNodePositions;

        PositionsLayoutFactory(LayoutFactory delegateLayoutFactory,
                               Map<String, Point> initialNodePositions,
                               Set<String> nodesWithFixedPositions,
                               Map<String, Point> fixedNodePositions
        ) {
            this.delegateLayoutFactory = delegateLayoutFactory;
            this.initialNodePositions = initialNodePositions;
            this.nodesWithFixedPositions = nodesWithFixedPositions;
            this.fixedNodePositions = fixedNodePositions;
        }

        public LayoutResult getLayoutResult() {
            return layoutResult;
        }

        @Override
        public Layout create() {
            final Layout delegateLayout = delegateLayoutFactory.create();
            return new Layout() {
                @Override
                public void run(Graph graph, LayoutParameters layoutParameters) {
                    if (!initialNodePositions.isEmpty()) {
                        delegateLayout.setInitialNodePositions(initialNodePositions);
                    }
                    if (!nodesWithFixedPositions.isEmpty()) {
                        delegateLayout.setNodesWithFixedPosition(nodesWithFixedPositions);
                    }
                    // only if not empty,
                    // setting nodes with fixed node positions will invalidate previous nodes with fixed positions
                    if (!fixedNodePositions.isEmpty() && delegateLayout instanceof AbstractLayout) {
                        ((AbstractLayout) delegateLayout).setFixedNodePositions(fixedNodePositions);
                    }
                    delegateLayout.run(graph, layoutParameters);
                    layoutResult.positions = graph.getNodePositions();
                }

                @Override
                public void setInitialNodePositions(Map<String, Point> initialNodePositions) {
                    throw new PowsyblException("not implemented");
                }

                @Override
                public void setNodesWithFixedPosition(Set<String> nodesWithFixedPosition) {
                    throw new PowsyblException("not implemented");
                }

                @Override
                public Map<String, Point> getInitialNodePositions() {
                    throw new PowsyblException("not implemented");
                }

                @Override
                public Set<String> getNodesWithFixedPosition() {
                    throw new PowsyblException("not implemented");
                }

                @Override
                public void setTextNodeFixedPosition(String voltageLevelId, Point topLeft, Point edgeConnection) {
                    throw new PowsyblException("not implemented");
                }
            };
        }
    }
}