gonum.org/v1/gonum@v0.14.0/graph/layout/eades.go (about)

     1  // Copyright ©2019 The Gonum Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package layout
     6  
     7  import (
     8  	"math"
     9  
    10  	"golang.org/x/exp/rand"
    11  
    12  	"gonum.org/v1/gonum/graph"
    13  	"gonum.org/v1/gonum/spatial/barneshut"
    14  	"gonum.org/v1/gonum/spatial/r2"
    15  )
    16  
    17  // EadesR2 implements the graph layout algorithm essentially as
    18  // described in "A heuristic for graph drawing", Congressus
    19  // numerantium 42:149-160.
    20  // The implementation here uses the Barnes-Hut approximation for
    21  // global repulsion calculation, and edge weights are considered
    22  // when calculating adjacent node attraction.
    23  type EadesR2 struct {
    24  	// Updates is the number of updates to perform.
    25  	Updates int
    26  
    27  	// Repulsion is the strength of the global
    28  	// repulsive force between nodes in the
    29  	// layout. It corresponds to C3 in the paper.
    30  	Repulsion float64
    31  
    32  	// Rate is the gradient descent rate. It
    33  	// corresponds to C4 in the paper.
    34  	Rate float64
    35  
    36  	// Theta is the Barnes-Hut theta constant.
    37  	Theta float64
    38  
    39  	// Src is the source of randomness used
    40  	// to initialize the nodes' locations. If
    41  	// Src is nil, the global random number
    42  	// generator is used.
    43  	Src rand.Source
    44  
    45  	nodes   graph.Nodes
    46  	indexOf map[int64]int
    47  
    48  	particles []barneshut.Particle2
    49  	forces    []r2.Vec
    50  }
    51  
    52  // Update is the EadesR2 spatial graph update function.
    53  func (u *EadesR2) Update(g graph.Graph, layout LayoutR2) bool {
    54  	if u.Updates <= 0 {
    55  		return false
    56  	}
    57  	u.Updates--
    58  
    59  	if !layout.IsInitialized() {
    60  		var rnd func() float64
    61  		if u.Src == nil {
    62  			rnd = rand.Float64
    63  		} else {
    64  			rnd = rand.New(u.Src).Float64
    65  		}
    66  		u.nodes = g.Nodes()
    67  		u.indexOf = make(map[int64]int, u.nodes.Len())
    68  		if u.nodes.Len() >= 0 {
    69  			u.particles = make([]barneshut.Particle2, 0, u.nodes.Len())
    70  		}
    71  		for u.nodes.Next() {
    72  			id := u.nodes.Node().ID()
    73  			u.indexOf[id] = len(u.particles)
    74  			u.particles = append(u.particles, eadesR2Node{id: id, pos: r2.Vec{X: rnd(), Y: rnd()}})
    75  		}
    76  		u.forces = make([]r2.Vec, len(u.particles))
    77  	}
    78  	u.nodes.Reset()
    79  
    80  	// Apply global repulsion.
    81  	plane, err := barneshut.NewPlane(u.particles)
    82  	if err != nil {
    83  		return false
    84  	}
    85  	var updated bool
    86  	for i, p := range u.particles {
    87  		f := r2.Scale(-u.Repulsion, plane.ForceOn(p, u.Theta, barneshut.Gravity2))
    88  		// Prevent marginal updates that can be caused by
    89  		// floating point error when nodes are very far apart.
    90  		if math.Hypot(f.X, f.Y) > 1e-12 {
    91  			updated = true
    92  		}
    93  		u.forces[i] = f
    94  	}
    95  
    96  	// Handle edge weighting for attraction.
    97  	var weight func(uid, vid int64) float64
    98  	if wg, ok := g.(graph.Weighted); ok {
    99  		if _, ok := g.(graph.Directed); ok {
   100  			weight = func(xid, yid int64) float64 {
   101  				var w float64
   102  				f, ok := wg.Weight(xid, yid)
   103  				if ok {
   104  					w += f
   105  				}
   106  				r, ok := wg.Weight(yid, xid)
   107  				if ok {
   108  					w += r
   109  				}
   110  				return w
   111  			}
   112  		} else {
   113  			weight = func(xid, yid int64) float64 {
   114  				w, ok := wg.Weight(xid, yid)
   115  				if ok {
   116  					return w
   117  				}
   118  				return 0
   119  			}
   120  		}
   121  	} else {
   122  		// This is only called when the adjacency is known so just return unit.
   123  		weight = func(_, _ int64) float64 { return 1 }
   124  	}
   125  
   126  	seen := make(map[[2]int64]bool)
   127  	for u.nodes.Next() {
   128  		xid := u.nodes.Node().ID()
   129  		xidx := u.indexOf[xid]
   130  		to := g.From(xid)
   131  		for to.Next() {
   132  			yid := to.Node().ID()
   133  			if seen[[2]int64{xid, yid}] {
   134  				continue
   135  			}
   136  			seen[[2]int64{yid, xid}] = true
   137  			yidx := u.indexOf[yid]
   138  
   139  			// Apply adjacent node attraction.
   140  			v := r2.Sub(u.particles[yidx].Coord2(), u.particles[xidx].Coord2())
   141  			f := r2.Scale(weight(xid, yid)*math.Log(math.Hypot(v.X, v.Y)), v)
   142  			if math.IsInf(f.X, 0) || math.IsInf(f.Y, 0) {
   143  				return false
   144  			}
   145  			if math.Hypot(f.X, f.Y) > 1e-12 {
   146  				updated = true
   147  			}
   148  			u.forces[xidx] = r2.Add(u.forces[xidx], f)
   149  			u.forces[yidx] = r2.Sub(u.forces[yidx], f)
   150  		}
   151  	}
   152  
   153  	if !updated {
   154  		return false
   155  	}
   156  
   157  	rate := u.Rate
   158  	if rate == 0 {
   159  		rate = 0.1
   160  	}
   161  	for i, f := range u.forces {
   162  		n := u.particles[i].(eadesR2Node)
   163  		n.pos = r2.Add(n.pos, r2.Scale(rate, f))
   164  		u.particles[i] = n
   165  		layout.SetCoord2(n.id, n.pos)
   166  	}
   167  	return true
   168  }
   169  
   170  type eadesR2Node struct {
   171  	id  int64
   172  	pos r2.Vec
   173  }
   174  
   175  func (p eadesR2Node) Coord2() r2.Vec { return p.pos }
   176  func (p eadesR2Node) Mass() float64  { return 1 }