A Revised boingDistribute for the SWARM GraphLib
Contributed by Dr. Paul E. Girard
Science Applications International Corporation (SAIC)
10260 Campus Point Drive, M/S C-3
San Diego, CA 92121
VOX (619) 535-7158
FAX (619) 546-6223
e-mail paul_girard@cpqm.saic.com

Summary

There is a boingDistribute() method in the Digraph class of the
GraphLib-0.2 which tries to find equilibrium among nodes with links
treated as springs and nodes repelling each other. It iteratively
calls the boingStep() method to adjust node locations. The previous
version of the boingStep() method was translated from a Java version,
called relax(), believed to be from the Tk/Tcl library (?) That
algorithm (and its SWARM/GraphLib version) did not converge well and,
upon investigation, was found to have been formulated incorrectly from
a physics perspective. A revised version provides a new algorithm in
the "boingStep" method. The following is a description of the
equations implemented by the revised code. The method code is embedded
in a demonstration configuration described later. Source code is
provided to augment the GraphLib.

Problem

Given a set of nodes and links, distribute the nodes so that the
topology is simple(r), meaning that neighbor nodes (in a direct
connection sense) are close together spatially and there are few(er)
crossing links. (This algorithm does not necessarilty achieve this
goal, but can get close and, with some help from the user, perhaps
even closer. Stable configurations do NOT necessarily realize the
least number of arc crossings. Configurations meeting this criterion
may be unstable.)

The approach is to assume that the behavior is mimicked by springs in
place of links between some node pairs, and repulsion in a negative
gravity sense among all nodes. To get a balance of forces on a network
consisting of nodes (bodies with mass = 1) and links (springs with a
spring length and a spring constant), where the string tension is
proportional to the stretch (or compression) of the spring, and the
nodes repel each other proportionally (by a repulsion factor, like a
negative gravitational constant) as the inverse of the square of their
distance.

The force on each node is the sum of the spring attraction (or spring
repulsion, if the spring is compressed below its spring length) for
each link connecting it to any other nodes, plus the sum of the
negative gravity repulsions from all other nodes. (NOTE: It is not
necessary, as was done in a previous version of this algorithm, to
eliminate from consideration, nodes that are far away. The effort in
computing the distance between the pairs will have already been
expended. Testing whether this distance exceeds a threshhold costs as
much as just multiplying the result and adding the repulsion force
indicated. (Besides, the previous version treated this aspect
incorrectly.)

Spring Forces

The spring force is proportional to the difference between the current
spring length, "length", and the relaxed (untensioned) spring length,
"L". The proportionality constant is the spring constant, "K". (K is
set, nominally and arbitrarily, but empirically, to 0.25, in the
submitted code.)  (L is known to be set to 100 in the program calling
the boingDistribute method.)

Fs = K * (L - length).

This value will be negative when the spring is stretched. The
component of this force in the x (resp., y) direction is

 dx = Fs * vx / length (resp., dy = Fs * vy / length), 

where vx (vy) is the difference in the x (y) locations of the two
nodes in the direction toward the node under consideration.

The total force on each node (in the respective component direction),
due to spring tension is summed over all links (graph edges), applying
positive or negative values of dx (dy) to the nodes on each end of the
link.

nodedxi(spring) = sum over j for linksij (dxij)

Negative Gravity Forces

It is assumed that negative gravity acts inversely as the square of
the distance between bodies (nodes) and that the masses of the nodes
are 1.0, and that there is a repulsion factor, R (R is set, nominally
and arbitrarily, but empirically, to 10000, in the submitted code), so
that

Fr = R / length^2.

The component of this force in the x (resp., y) direction is (just as above):

dx = Fr * vx / length (resp., dy = Fr * vy / length), = R * vx  / (length^3)

by substituting for Fr, and where vx (resp., vy) is the difference in
the x (resp., y) locations of the two nodes in the direction away from
the node under consideration.

The total force on each node (in the respective component direction),
due to negative gravity repulsion, is obtained by summing over all
other nodes.

nodedxi(repel) = sum over j for all nodes j (except j = i ) (dxij).

Net Forces

Adding the components of both forces (both nodexi's and both nodeyi's)
together, and equating that force to the distance to move each step,
yields the component movement in each direction.

This method will find local minima in the total counterbalancing sets
of forces, resulting in a net force of zero when all movement
stops. The calls to boingStep could be stopped when the amount of
movement (expressed by the return variable, vx) falls below a
threshold. The algorithm settles down as described below.

But there may be other minima with different configurations. If the
program in which this method is embedded has a "shake" method it may
be powerful enough to push the configuration to another minima. If the
program in which this method is embedded has the capability to allow
the user to drag the nodes around the canvas, this may be used to find
other possible configurations. The set of stable configurations is
sometimes sparse and other times dense. For example, after getting
nodes 0 and 6 out to the side from node 9, one can reposition 0 and 6
in a triangle at different angles from 9.  Notice that moving node 2
inside the triangle formed by 1, 7 and 8, while it reduces the number
of arc crossings, produces an unstable configuration and 2 will drift
out of the triangle.

SWARM Version

The demonstration is provided in a file named
newBoingDemo.tar.gz. This uncompresses and extracts to a folder named
GraphBoing, inside of which is contained the souce and executable. The
source code includes the ObserverSwarm and ModelSwarm which create,
display and control an instance of a "BoingGraph" class object for the
demo. BoingGraph is descended from the DiGraph class in the
GraphLib-0.2 library. The executable is called statBoingGraph, because
the random addition and deletion of nodes was removed from the
dynamicGraph implementation..

The demonstration allows the user to set "population" (the number of
randomly generated nodes), and "maxK" (the maximum number of randomly
generated links *from* each node). The attribute "eventRate" is not
used in this demo, because the step() method of the ModelSwarm (copied
from dynamicGraph) has been gutted for this demo. When the user
executes the "Time Step" method in the SWARM Control Panel, the
BoingGraph canvas is launched along with the probe for the
BoingGraph. The BoingGraph probe allows the user to change the
springLength, springConstant and repelFactor from the default values,
which can be used to tune the behavior of the boingStep method. The
user may also use the ObserverSwarm probe to execute the
ringDistribute method from the original dynamicGraph demo. The
boingDistribute method button causes the new boingStep to be
called. The boingDistribute method does 40 iterations of the boingStep
method, the results of which are displayed in the canvas. Usually more
than one boingDistribute execution is necesary. Alternatively, pushing
"Go" does boingStep continuously. The graph will settle down to the
condition that no movement is discernible, but the algorith is still
calculating the results, but with zero effect. In dynamic ("Go") or
still ("Stop") mode, the user can grab any node and drag it around the
canvas. In "Stop" mode, the user can change the boingStep control
attribute values. Getting too much springConstant vs. repelFactor
causes interesting vibrations during evolution.

Contribution to SWARM

The code will be uploaded to the SWARM ftp site for possible inclusion
in the GraphLib of a future release. In the BoingGraph.m source file,
the boingStep method replaces the one in DiGraph and some lines in
create() need to be added to the Digraph class (initializing
springConstant, repelFactor, etc.). Otherwise, code which is commented
out is duplicative of that in DiGraph. No changes were necessary to
theGraphNode and GraphLink classes.

There is a note included in the boingStep code that points out that
there was a need for an addition of 0.5 to the value to use in moving
the NodeItem (icon). Otherwise, the graph tended to drift to the upper
left. An addition of 1.0 moved it to the lower right. There may be a
round-off problem in using the getX (getY) method or the moveX (moveY)
methods in NodeItem.m, or elsewhere. It is probably that move*
truncates the value passed in to a (long). Adding 0.5, before moving
it, effectively rounds. This might be a useful note to include in
DiGraph and/or NodeItem, or change the code in NodeItem to round
instead of truncating.

Enhanced versions, which let one set springLength, springConstant and
a new attribute, nodeMass, separately for each link and node, would be
interesting. Changing repulsion to gravity and removing links would
yield a 2-D universe model. 3-D would be interesting, too.

Java version

The Java version has the same corrections. It is the version which was
demonstrated at SWARMFest '98, in the GraphLayout folder generated
from GraphLayout.tar.gz. The source code file is Graph.java. The old
code is in Graph.java.orig. The only changes are in the
function/method relax().


