GraphVizGraphBuilder.java

/**
 * Copyright (c) 2023, 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.openloadflow.network;

import com.powsybl.openloadflow.util.PerUnit;
import org.anarres.graphviz.builder.*;

import java.util.Locale;
import java.util.Objects;

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

    private final LfNetwork network;

    public GraphVizGraphBuilder(LfNetwork network) {
        this.network = Objects.requireNonNull(network);
    }

    private static String getNodeLabel(LfBus bus) {
        StringBuilder builder = new StringBuilder(Integer.toString(bus.getNum()))
                .append("\n")
                .append(bus.getId());
        if (bus.getGenerationTargetP() != 0 || bus.getGenerationTargetQ() != 0) {
            builder.append("\ngen=")
                    .append(String.format(Locale.US, "%.1f", bus.getGenerationTargetP() * PerUnit.SB)).append(" MW ")
                    .append(String.format(Locale.US, "%.1f", bus.getGenerationTargetQ() * PerUnit.SB)).append(" MVar");
        }
        if (bus.getLoadTargetP() != 0 || bus.getLoadTargetQ() != 0) {
            builder.append("\nload=")
                    .append(String.format(Locale.US, "%.1f", bus.getLoadTargetP() * PerUnit.SB)).append(" MW ")
                    .append(String.format(Locale.US, "%.1f", bus.getLoadTargetQ() * PerUnit.SB)).append(" MVar");
        }
        return builder.toString();
    }

    private static String getEdgeLabel(LfBranch branch) {
        StringBuilder builder = new StringBuilder(branch.getId());
        PiModel piModel = branch.getPiModel();
        if (piModel.getR1() != 1) {
            builder.append("\nr1=").append(String.format(Locale.US, "%.3f", piModel.getR1()));
        }
        if (piModel.getA1() != 0) {
            builder.append("\na1=").append(String.format(Locale.US, "%.3f", piModel.getA1()));
        }
        return builder.toString();
    }

    private static String getEdgeColor(LfBranch branch, LoadFlowModel loadFlowModel) {
        if (branch.isZeroImpedance(loadFlowModel)) {
            return branch.isSpanningTreeEdge(loadFlowModel) ? "red" : "orange";
        }
        return "black";
    }

    public GraphVizGraph build(LoadFlowModel loadFlowModel) {
        GraphVizGraph graph = new GraphVizGraph().label(network.getId());
        GraphVizScope scope = new GraphVizScope.Impl();
        for (LfBus bus : network.getBuses()) {
            graph.node(scope, bus.getNum())
                    .label(getNodeLabel(bus))
                    .attr(GraphVizAttribute.shape, "box")
                    .attr(GraphVizAttribute.style, "filled,rounded")
                    .attr(GraphVizAttribute.fontsize, "10")
                    .attr(GraphVizAttribute.fillcolor, "grey");
        }
        // draw voltage controller -> controlled links
        for (LfBus bus : network.getBuses()) {
            if (bus.isGeneratorVoltageControlled()) {
                GeneratorVoltageControl vc = bus.getGeneratorVoltageControl().orElseThrow();
                for (LfBus controllerBus : vc.getControllerElements()) {
                    GraphVizEdge edge = graph.edge(scope, controllerBus.getNum(), bus.getNum(), controllerBus);
                    edge.attr(GraphVizAttribute.color, "lightgray")
                            .attr(GraphVizAttribute.style, "dotted");
                }
            }
        }
        for (LfBranch branch : network.getBranches()) {
            LfBus bus1 = branch.getBus1();
            LfBus bus2 = branch.getBus2();
            if (bus1 != null && bus2 != null) {
                GraphVizEdge edge = graph.edge(scope, bus1.getNum(), bus2.getNum(), branch.getNum());
                edge.label().append(getEdgeLabel(branch));
                edge.attr(GraphVizAttribute.color, getEdgeColor(branch, loadFlowModel))
                        .attr(GraphVizAttribute.style, branch.isDisabled() ? "dashed" : "")
                        .attr(GraphVizAttribute.dir, "none");
            }
        }
        return graph;
    }
}