Skip to content
This repository has been archived by the owner on Nov 18, 2022. It is now read-only.

Commit

Permalink
Allow applying repulsion force between springs (#93)
Browse files Browse the repository at this point in the history
* allow applying repulsion force between springs
* spring repulsion as a factor of repulsion between points
* unit test
* Apply repulsion between node and spring also to end nodes of spring

Signed-off-by: Luma <zamarrenolm@aia.es>
  • Loading branch information
zamarrenolm authored Oct 20, 2022
1 parent 7c07e91 commit 996e2ce
Show file tree
Hide file tree
Showing 7 changed files with 1,921 additions and 6 deletions.
63 changes: 58 additions & 5 deletions src/main/java/com/powsybl/forcelayout/ForceLayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,15 @@ public class ForceLayout<V, E> {
private static final double DEFAULT_REPULSION = 800.0;
private static final double DEFAULT_FRICTION = 500;
private static final double DEFAULT_MAX_SPEED = 100;
private static final double DEFAULT_SPRING_REPULSION_FACTOR = 0.0; // Disabled by default

private int maxSteps;
private double minEnergyThreshold;
private double deltaTime;
private double repulsion;
private double friction;
private double maxSpeed;
private double springRepulsionFactor;

private final Graph<V, E> graph;
private final Map<V, Point> points = new LinkedHashMap<>();
Expand All @@ -82,6 +84,7 @@ public ForceLayout(Graph<V, E> graph) {
this.repulsion = DEFAULT_REPULSION;
this.friction = DEFAULT_FRICTION;
this.maxSpeed = DEFAULT_MAX_SPEED;
this.springRepulsionFactor = DEFAULT_SPRING_REPULSION_FACTOR;

this.graph = Objects.requireNonNull(graph);
}
Expand Down Expand Up @@ -116,6 +119,11 @@ public ForceLayout<V, E> setMaxSpeed(double maxSpeed) {
return this;
}

public ForceLayout<V, E> setSpringRepulsionFactor(double springRepulsionFactor) {
this.springRepulsionFactor = springRepulsionFactor;
return this;
}

private void initializePoints() {
for (V vertex : graph.vertexSet()) {
points.put(vertex, new Point(random.nextDouble(), random.nextDouble()));
Expand All @@ -140,7 +148,10 @@ public void execute() {

int i;
for (i = 0; i < maxSteps; i++) {
applyCoulombsLaw();
applyCoulombsLawToPoints();
if (springRepulsionFactor != 0.0) {
applyCoulombsLawToSprings();
}
applyHookesLaw();
attractToCenter();
updateVelocity();
Expand All @@ -159,15 +170,57 @@ public void execute() {
LOGGER.info("Elapsed time: {}", elapsedTime / 1e9);
}

private void applyCoulombsLaw() {
private Vector coulombsForce(Vector p1, Vector p2, double repulsion) {
Vector distance = p1.subtract(p2);
Vector direction = distance.normalize();
return direction.multiply(repulsion).divide(distance.magnitudeSquare() * 0.5 + 0.1);
}

private void applyCoulombsLawToPoints() {
for (Point point : points.values()) {
Vector p = point.getPosition();
for (Point otherPoint : points.values()) {
if (!point.equals(otherPoint)) {
Vector distance = point.getPosition().subtract(otherPoint.getPosition());
Vector direction = distance.normalize();
point.applyForce(coulombsForce(p, otherPoint.getPosition(), repulsion));
}
}
}
}

Vector force = direction.multiply(repulsion).divide(distance.magnitudeSquare() * 0.5 + 0.1);
private void applyCoulombsLawToSprings() {
for (Point point : points.values()) {
Vector p = point.getPosition();
for (Spring spring : springs) {
Point n1 = spring.getNode1();
Point n2 = spring.getNode2();
if (!n1.equals(point) && !n2.equals(point)) {
Vector q1 = spring.getNode1().getPosition();
Vector q2 = spring.getNode2().getPosition();
Vector center = q1.add(q2.subtract(q1).multiply(0.5));
Vector force = coulombsForce(p, center, repulsion * springRepulsionFactor);
point.applyForce(force);
n1.applyForce(force.multiply(-0.5));
n2.applyForce(force.multiply(-0.5));
}
}
}
for (Spring spring : springs) {
Point n1 = spring.getNode1();
Point n2 = spring.getNode2();
Vector p1 = spring.getNode1().getPosition();
Vector p2 = spring.getNode2().getPosition();
Vector center = p1.add(p2.subtract(p1).multiply(0.5));
for (Spring otherSpring : springs) {
if (!spring.equals(otherSpring)) {
// Compute the repulsion force between centers of the springs
Vector op1 = otherSpring.getNode1().getPosition();
Vector op2 = otherSpring.getNode2().getPosition();
Vector otherCenter = op1.add(op2.subtract(op1).multiply(0.5));
Vector force = coulombsForce(center, otherCenter, repulsion * springRepulsionFactor);

// And apply it to both points of the spring
n1.applyForce(force);
n2.applyForce(force);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/powsybl/nad/layout/BasicForceLayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class BasicForceLayout extends AbstractLayout {
protected void nodesLayout(Graph graph, LayoutParameters layoutParameters) {
org.jgrapht.Graph<Node, Edge> jgraphtGraph = graph.getJgraphtGraph(layoutParameters.isTextNodesForceLayout());
ForceLayout<Node, Edge> forceLayout = new ForceLayout<>(jgraphtGraph);
forceLayout.setSpringRepulsionFactor(layoutParameters.getSpringRepulsionFactorForceLayout());
forceLayout.execute();

jgraphtGraph.vertexSet().forEach(node -> {
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/powsybl/nad/layout/LayoutParameters.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
*/
public class LayoutParameters {
private boolean textNodesForceLayout = false;
private double springRepulsionFactorForceLayout = 0.0;

public LayoutParameters() {
}

public LayoutParameters(LayoutParameters other) {
this.textNodesForceLayout = other.textNodesForceLayout;
this.springRepulsionFactorForceLayout = other.springRepulsionFactorForceLayout;
}

public boolean isTextNodesForceLayout() {
Expand All @@ -27,4 +29,13 @@ public LayoutParameters setTextNodesForceLayout(boolean textNodesForceLayout) {
this.textNodesForceLayout = textNodesForceLayout;
return this;
}

public LayoutParameters setSpringRepulsionFactorForceLayout(double springRepulsionFactorForceLayout) {
this.springRepulsionFactorForceLayout = springRepulsionFactorForceLayout;
return this;
}

public double getSpringRepulsionFactorForceLayout() {
return springRepulsionFactorForceLayout;
}
}
170 changes: 170 additions & 0 deletions src/test/java/com/powsybl/nad/layout/ForceLayoutTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/**
* 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.nad.layout;

import com.powsybl.iidm.network.*;
import com.powsybl.nad.AbstractTest;
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 org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

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

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

@BeforeEach
public void setup() {
setLayoutParameters(new LayoutParameters().setTextNodesForceLayout(false));
setSvgParameters(new SvgParameters()
.setInsertNameDesc(false)
.setSvgWidthAndHeightAdded(false));
}

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

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

@Test
void testDiamondNoSpringRepulsionFactor() {
assertEquals(
toString("/diamond-spring-repulsion-factor-0.0.svg"),
generateSvgString(createDiamondNetwork(), "/diamond-spring-repulsion-factor-0.0.svg"));
}

@Test
void testDiamondSmallSpringRepulsionFactor() {
getLayoutParameters().setSpringRepulsionFactorForceLayout(0.2);
assertEquals(
toString("/diamond-spring-repulsion-factor-0.2.svg"),
generateSvgString(createDiamondNetwork(), "/diamond-spring-repulsion-factor-0.2.svg"));
}

static Network createDiamondNetwork() {
Network network = NetworkFactory.findDefault().createNetwork("diamond", "manual");
network.setName("diamond");

Substation subA = network.newSubstation().setId("A").add();
Bus subA400 = createBus(subA, 400);
Bus subA230 = createBus(subA, 230);
createTransformer(subA400, subA230);

Substation subB = network.newSubstation().setId("B").add();
Bus subB230 = createBus(subB, 230);
createLine(subA230, subB230);

Substation subC = network.newSubstation().setId("C").add();
Bus subC230 = createBus(subC, 230);
Bus subC66 = createBus(subC, 66);
Bus subC20 = createBus(subC, 20);
createTransformer(subC230, subC66);
createTransformer(subC66, subC20);
createLine(subB230, subC230);

Substation subD = network.newSubstation().setId("D").add();
Bus subD66 = createBus(subD, 66);
Bus subD10 = createBus(subD, 10);
createTransformer(subD66, subD10);
createLine(subC66, subD66);

Substation subE = network.newSubstation().setId("E").add();
Bus subE10 = createBus(subE, 10);
createLine(subD10, subE10);

Bus subF10 = createBus(network, "F", 10);
Bus subG10 = createBus(network, "G", 10);
Bus subH10 = createBus(network, "H", 10);
Bus subI10 = createBus(network, "I", 10);
Bus subJ10 = createBus(network, "J", 10);
Bus subK10 = createBus(network, "K", 10);

createLine(subE10, subF10);
createLine(subF10, subG10);
createLine(subG10, subH10);
createLine(subH10, subD10);

createLine(subF10, subI10);
createLine(subI10, subJ10);
createLine(subJ10, subK10);
createLine(subK10, subD10);

return network;
}

private static Bus createBus(Network network, String substationId, double nominalVoltage) {
Substation substation = network.newSubstation().setId(substationId).add();
return createBus(substation, nominalVoltage);
}

private static Bus createBus(Substation substation, double nominalVoltage) {
String vlId = String.format("%s %.0f", substation.getId(), nominalVoltage);
String busId = String.format("%s %s", vlId, "Bus");
return substation.newVoltageLevel()
.setId(vlId)
.setNominalV(nominalVoltage)
.setTopologyKind(TopologyKind.BUS_BREAKER)
.add()
.getBusBreakerView()
.newBus()
.setId(busId)
.add();
}

private static void createTransformer(Bus bus1, Bus bus2) {
Substation substation = bus1.getVoltageLevel().getSubstation().orElseThrow();
String id = String.format("%s %.0f %.0f",
substation.getId(),
bus1.getVoltageLevel().getNominalV(),
bus2.getVoltageLevel().getNominalV());
substation.newTwoWindingsTransformer().setId(id)
.setR(0.0)
.setX(1.0)
.setG(0.0)
.setB(0.0)
.setVoltageLevel1(bus1.getVoltageLevel().getId())
.setVoltageLevel2(bus2.getVoltageLevel().getId())
.setConnectableBus1(bus1.getId())
.setConnectableBus2(bus2.getId())
.setRatedU1(bus1.getVoltageLevel().getNominalV())
.setRatedU2(bus2.getVoltageLevel().getNominalV())
.setBus1(bus1.getId())
.setBus2(bus2.getId())
.add();
}

private static void createLine(Bus bus1, Bus bus2) {
String id = String.format("%s - %s",
bus1.getVoltageLevel().getSubstation().orElseThrow().getId(),
bus2.getVoltageLevel().getSubstation().orElseThrow().getId());
bus1.getNetwork().newLine().setId(id)
.setR(0.0)
.setX(1.0)
.setG1(0.0)
.setB1(0.0)
.setG2(0.0)
.setB2(0.0)
.setVoltageLevel1(bus1.getVoltageLevel().getId())
.setVoltageLevel2(bus2.getVoltageLevel().getId())
.setConnectableBus1(bus1.getId())
.setConnectableBus2(bus2.getId())
.setBus1(bus1.getId())
.setBus2(bus2.getId())
.add();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ class LayoutParametersTest {
@Test
void test() {
LayoutParameters layoutParameters0 = new LayoutParameters()
.setTextNodesForceLayout(true);
.setTextNodesForceLayout(true)
.setSpringRepulsionFactorForceLayout(1.0);

LayoutParameters layoutParameters1 = new LayoutParameters(layoutParameters0);

assertEquals(layoutParameters0.isTextNodesForceLayout(), layoutParameters1.isTextNodesForceLayout());
assertEquals(layoutParameters0.getSpringRepulsionFactorForceLayout(), layoutParameters1.getSpringRepulsionFactorForceLayout());
}
}
Loading

0 comments on commit 996e2ce

Please sign in to comment.