1 // Copyright 2004 The Trustees of Indiana University.
3 // Distributed under the Boost Software License, Version 1.0.
4 // (See accompanying file LICENSE_1_0.txt or copy at
5 // http://www.boost.org/LICENSE_1_0.txt)
7 // Authors: Douglas Gregor
9 #ifndef BOOST_GRAPH_KAMADA_KAWAI_SPRING_LAYOUT_HPP
10 #define BOOST_GRAPH_KAMADA_KAWAI_SPRING_LAYOUT_HPP
12 #include <boost/graph/graph_traits.hpp>
13 #include <boost/graph/johnson_all_pairs_shortest.hpp>
14 #include <boost/type_traits/is_convertible.hpp>
18 #include <boost/limits.hpp>
22 namespace detail { namespace graph {
24 * Denotes an edge or display area side length used to scale a
25 * Kamada-Kawai drawing.
27 template<bool Edge, typename T>
30 explicit edge_or_side(T value) : value(value) {}
36 * Compute the edge length from an edge length. This is trivial.
38 template<typename Graph, typename DistanceMap, typename IndexMap,
40 T compute_edge_length(const Graph&, DistanceMap, IndexMap,
41 edge_or_side<true, T> length)
42 { return length.value; }
45 * Compute the edge length based on the display area side
46 length. We do this by dividing the side length by the largest
47 shortest distance between any two vertices in the graph.
49 template<typename Graph, typename DistanceMap, typename IndexMap,
52 compute_edge_length(const Graph& g, DistanceMap distance, IndexMap index,
53 edge_or_side<false, T> length)
57 typedef typename graph_traits<Graph>::vertex_iterator vertex_iterator;
59 for (vertex_iterator ui = vertices(g).first, end = vertices(g).second;
61 vertex_iterator vi = ui;
62 for (++vi; vi != end; ++vi) {
63 T dij = distance[get(index, *ui)][get(index, *vi)];
64 if (dij > result) result = dij;
67 return length.value / result;
71 * Implementation of the Kamada-Kawai spring layout algorithm.
73 template<typename Graph, typename PositionMap, typename WeightMap,
74 typename EdgeOrSideLength, typename Done,
75 typename VertexIndexMap, typename DistanceMatrix,
76 typename SpringStrengthMatrix, typename PartialDerivativeMap>
77 struct kamada_kawai_spring_layout_impl
79 typedef typename property_traits<WeightMap>::value_type weight_type;
80 typedef std::pair<weight_type, weight_type> deriv_type;
81 typedef typename graph_traits<Graph>::vertex_iterator vertex_iterator;
82 typedef typename graph_traits<Graph>::vertex_descriptor
85 kamada_kawai_spring_layout_impl(
89 EdgeOrSideLength edge_or_side_length,
91 weight_type spring_constant,
93 DistanceMatrix distance,
94 SpringStrengthMatrix spring_strength,
95 PartialDerivativeMap partial_derivatives)
96 : g(g), position(position), weight(weight),
97 edge_or_side_length(edge_or_side_length), done(done),
98 spring_constant(spring_constant), index(index), distance(distance),
99 spring_strength(spring_strength),
100 partial_derivatives(partial_derivatives) {}
102 // Compute contribution of vertex i to the first partial
103 // derivatives (dE/dx_m, dE/dy_m) (for vertex m)
105 compute_partial_derivative(vertex_descriptor m, vertex_descriptor i)
107 #ifndef BOOST_NO_STDC_NAMESPACE
109 #endif // BOOST_NO_STDC_NAMESPACE
111 deriv_type result(0, 0);
113 weight_type x_diff = position[m].x - position[i].x;
114 weight_type y_diff = position[m].y - position[i].y;
115 weight_type dist = sqrt(x_diff * x_diff + y_diff * y_diff);
116 result.first = spring_strength[get(index, m)][get(index, i)]
117 * (x_diff - distance[get(index, m)][get(index, i)]*x_diff/dist);
118 result.second = spring_strength[get(index, m)][get(index, i)]
119 * (y_diff - distance[get(index, m)][get(index, i)]*y_diff/dist);
125 // Compute partial derivatives dE/dx_m and dE/dy_m
127 compute_partial_derivatives(vertex_descriptor m)
129 #ifndef BOOST_NO_STDC_NAMESPACE
131 #endif // BOOST_NO_STDC_NAMESPACE
133 deriv_type result(0, 0);
135 // TBD: looks like an accumulate to me
136 std::pair<vertex_iterator, vertex_iterator> verts = vertices(g);
137 for (/* no init */; verts.first != verts.second; ++verts.first) {
138 vertex_descriptor i = *verts.first;
139 deriv_type deriv = compute_partial_derivative(m, i);
140 result.first += deriv.first;
141 result.second += deriv.second;
147 // The actual Kamada-Kawai spring layout algorithm implementation
150 #ifndef BOOST_NO_STDC_NAMESPACE
152 #endif // BOOST_NO_STDC_NAMESPACE
154 // Compute d_{ij} and place it in the distance matrix
155 if (!johnson_all_pairs_shortest_paths(g, distance, index, weight,
159 // Compute L based on side length (if needed), or retrieve L
160 weight_type edge_length =
161 detail::graph::compute_edge_length(g, distance, index,
162 edge_or_side_length);
164 // Compute l_{ij} and k_{ij}
165 const weight_type K = spring_constant;
166 vertex_iterator ui, end = vertices(g).second;
167 for (ui = vertices(g).first; ui != end; ++ui) {
168 vertex_iterator vi = ui;
169 for (++vi; vi != end; ++vi) {
170 weight_type dij = distance[get(index, *ui)][get(index, *vi)];
171 if (dij == (std::numeric_limits<weight_type>::max)())
173 distance[get(index, *ui)][get(index, *vi)] = edge_length * dij;
174 distance[get(index, *vi)][get(index, *ui)] = edge_length * dij;
175 spring_strength[get(index, *ui)][get(index, *vi)] = K/(dij*dij);
176 spring_strength[get(index, *vi)][get(index, *ui)] = K/(dij*dij);
180 // Compute Delta_i and find max
181 vertex_descriptor p = *vertices(g).first;
182 weight_type delta_p(0);
184 for (ui = vertices(g).first; ui != end; ++ui) {
185 deriv_type deriv = compute_partial_derivatives(*ui);
186 put(partial_derivatives, *ui, deriv);
189 sqrt(deriv.first*deriv.first + deriv.second*deriv.second);
191 if (delta > delta_p) {
197 while (!done(delta_p, p, g, true)) {
198 // The contribution p makes to the partial derivatives of
199 // each vertex. Computing this (at O(n) cost) allows us to
200 // update the delta_i values in O(n) time instead of O(n^2)
202 std::vector<deriv_type> p_partials(num_vertices(g));
203 for (ui = vertices(g).first; ui != end; ++ui) {
204 vertex_descriptor i = *ui;
205 p_partials[get(index, i)] = compute_partial_derivative(i, p);
209 // Compute the 4 elements of the Jacobian
210 weight_type dE_dx_dx = 0, dE_dx_dy = 0, dE_dy_dx = 0, dE_dy_dy = 0;
211 for (ui = vertices(g).first; ui != end; ++ui) {
212 vertex_descriptor i = *ui;
214 weight_type x_diff = position[p].x - position[i].x;
215 weight_type y_diff = position[p].y - position[i].y;
216 weight_type dist = sqrt(x_diff * x_diff + y_diff * y_diff);
217 weight_type dist_cubed = dist * dist * dist;
218 weight_type k_mi = spring_strength[get(index,p)][get(index,i)];
219 weight_type l_mi = distance[get(index, p)][get(index, i)];
220 dE_dx_dx += k_mi * (1 - (l_mi * y_diff * y_diff)/dist_cubed);
221 dE_dx_dy += k_mi * l_mi * x_diff * y_diff / dist_cubed;
222 dE_dy_dx += k_mi * l_mi * x_diff * y_diff / dist_cubed;
223 dE_dy_dy += k_mi * (1 - (l_mi * x_diff * x_diff)/dist_cubed);
227 // Solve for delta_x and delta_y
228 weight_type dE_dx = get(partial_derivatives, p).first;
229 weight_type dE_dy = get(partial_derivatives, p).second;
231 weight_type delta_x =
232 (dE_dx_dy * dE_dy - dE_dy_dy * dE_dx)
233 / (dE_dx_dx * dE_dy_dy - dE_dx_dy * dE_dy_dx);
235 weight_type delta_y =
236 (dE_dx_dx * dE_dy - dE_dy_dx * dE_dx)
237 / (dE_dy_dx * dE_dx_dy - dE_dx_dx * dE_dy_dy);
240 // Move p by (delta_x, delta_y)
241 position[p].x += delta_x;
242 position[p].y += delta_y;
244 // Recompute partial derivatives and delta_p
245 deriv_type deriv = compute_partial_derivatives(p);
246 put(partial_derivatives, p, deriv);
249 sqrt(deriv.first*deriv.first + deriv.second*deriv.second);
250 } while (!done(delta_p, p, g, false));
252 // Select new p by updating each partial derivative and delta
253 vertex_descriptor old_p = p;
254 for (ui = vertices(g).first; ui != end; ++ui) {
255 deriv_type old_deriv_p = p_partials[get(index, *ui)];
256 deriv_type old_p_partial =
257 compute_partial_derivative(*ui, old_p);
258 deriv_type deriv = get(partial_derivatives, *ui);
260 deriv.first += old_p_partial.first - old_deriv_p.first;
261 deriv.second += old_p_partial.second - old_deriv_p.second;
263 put(partial_derivatives, *ui, deriv);
265 sqrt(deriv.first*deriv.first + deriv.second*deriv.second);
267 if (delta > delta_p) {
278 PositionMap position;
280 EdgeOrSideLength edge_or_side_length;
282 weight_type spring_constant;
283 VertexIndexMap index;
284 DistanceMatrix distance;
285 SpringStrengthMatrix spring_strength;
286 PartialDerivativeMap partial_derivatives;
288 } } // end namespace detail::graph
290 /// States that the given quantity is an edge length.
292 inline detail::graph::edge_or_side<true, T>
294 { return detail::graph::edge_or_side<true, T>(x); }
296 /// States that the given quantity is a display area side length.
298 inline detail::graph::edge_or_side<false, T>
300 { return detail::graph::edge_or_side<false, T>(x); }
303 * \brief Determines when to terminate layout of a particular graph based
304 * on a given relative tolerance.
306 template<typename T = double>
307 struct layout_tolerance
309 layout_tolerance(const T& tolerance = T(0.001))
310 : tolerance(tolerance), last_energy((std::numeric_limits<T>::max)()),
311 last_local_energy((std::numeric_limits<T>::max)()) { }
313 template<typename Graph>
315 operator()(T delta_p,
316 typename boost::graph_traits<Graph>::vertex_descriptor p,
321 if (last_energy == (std::numeric_limits<T>::max)()) {
322 last_energy = delta_p;
326 T diff = last_energy - delta_p;
327 if (diff < T(0)) diff = -diff;
328 bool done = (delta_p == T(0) || diff / last_energy < tolerance);
329 last_energy = delta_p;
332 if (last_local_energy == (std::numeric_limits<T>::max)()) {
333 last_local_energy = delta_p;
334 return delta_p == T(0);
337 T diff = last_local_energy - delta_p;
338 bool done = (delta_p == T(0) || (diff / last_local_energy) < tolerance);
339 last_local_energy = delta_p;
350 /** \brief Kamada-Kawai spring layout for undirected graphs.
352 * This algorithm performs graph layout (in two dimensions) for
353 * connected, undirected graphs. It operates by relating the layout
354 * of graphs to a dynamic spring system and minimizing the energy
355 * within that system. The strength of a spring between two vertices
356 * is inversely proportional to the square of the shortest distance
357 * (in graph terms) between those two vertices. Essentially,
358 * vertices that are closer in the graph-theoretic sense (i.e., by
359 * following edges) will have stronger springs and will therefore be
360 * placed closer together.
362 * Prior to invoking this algorithm, it is recommended that the
363 * vertices be placed along the vertices of a regular n-sided
366 * \param g (IN) must be a model of Vertex List Graph, Edge List
367 * Graph, and Incidence Graph and must be undirected.
369 * \param position (OUT) must be a model of Lvalue Property Map,
370 * where the value type is a class containing fields @c x and @c y
371 * that will be set to the @c x and @c y coordinates of each vertex.
373 * \param weight (IN) must be a model of Readable Property Map,
374 * which provides the weight of each edge in the graph @p g.
376 * \param edge_or_side_length (IN) provides either the unit length
377 * @c e of an edge in the layout or the length of a side @c s of the
378 * display area, and must be either @c boost::edge_length(e) or @c
379 * boost::side_length(s), respectively.
381 * \param done (IN) is a 4-argument function object that is passed
382 * the current value of delta_p (i.e., the energy of vertex @p p),
383 * the vertex @p p, the graph @p g, and a boolean flag indicating
384 * whether @p delta_p is the maximum energy in the system (when @c
385 * true) or the energy of the vertex being moved. Defaults to @c
386 * layout_tolerance instantiated over the value type of the weight
389 * \param spring_constant (IN) is the constant multiplied by each
390 * spring's strength. Larger values create systems with more energy
391 * that can take longer to stabilize; smaller values create systems
392 * with less energy that stabilize quickly but do not necessarily
393 * result in pleasing layouts. The default value is 1.
395 * \param index (IN) is a mapping from vertices to index values
396 * between 0 and @c num_vertices(g). The default is @c
397 * get(vertex_index,g).
399 * \param distance (UTIL/OUT) will be used to store the distance
400 * from every vertex to every other vertex, which is computed in the
401 * first stages of the algorithm. This value's type must be a model
402 * of BasicMatrix with value type equal to the value type of the
403 * weight map. The default is a a vector of vectors.
405 * \param spring_strength (UTIL/OUT) will be used to store the
406 * strength of the spring between every pair of vertices. This
407 * value's type must be a model of BasicMatrix with value type equal
408 * to the value type of the weight map. The default is a a vector of
411 * \param partial_derivatives (UTIL) will be used to store the
412 * partial derivates of each vertex with respect to the @c x and @c
413 * y coordinates. This must be a Read/Write Property Map whose value
414 * type is a pair with both types equivalent to the value type of
415 * the weight map. The default is an iterator property map.
417 * \returns @c true if layout was successful or @c false if a
418 * negative weight cycle was detected.
420 template<typename Graph, typename PositionMap, typename WeightMap,
421 typename T, bool EdgeOrSideLength, typename Done,
422 typename VertexIndexMap, typename DistanceMatrix,
423 typename SpringStrengthMatrix, typename PartialDerivativeMap>
425 kamada_kawai_spring_layout(
427 PositionMap position,
429 detail::graph::edge_or_side<EdgeOrSideLength, T> edge_or_side_length,
431 typename property_traits<WeightMap>::value_type spring_constant,
432 VertexIndexMap index,
433 DistanceMatrix distance,
434 SpringStrengthMatrix spring_strength,
435 PartialDerivativeMap partial_derivatives)
437 BOOST_STATIC_ASSERT((is_convertible<
438 typename graph_traits<Graph>::directed_category*,
442 detail::graph::kamada_kawai_spring_layout_impl<
443 Graph, PositionMap, WeightMap,
444 detail::graph::edge_or_side<EdgeOrSideLength, T>, Done, VertexIndexMap,
445 DistanceMatrix, SpringStrengthMatrix, PartialDerivativeMap>
446 alg(g, position, weight, edge_or_side_length, done, spring_constant,
447 index, distance, spring_strength, partial_derivatives);
454 template<typename Graph, typename PositionMap, typename WeightMap,
455 typename T, bool EdgeOrSideLength, typename Done,
456 typename VertexIndexMap>
458 kamada_kawai_spring_layout(
460 PositionMap position,
462 detail::graph::edge_or_side<EdgeOrSideLength, T> edge_or_side_length,
464 typename property_traits<WeightMap>::value_type spring_constant,
465 VertexIndexMap index)
467 typedef typename property_traits<WeightMap>::value_type weight_type;
469 typename graph_traits<Graph>::vertices_size_type n = num_vertices(g);
470 typedef std::vector<weight_type> weight_vec;
472 std::vector<weight_vec> distance(n, weight_vec(n));
473 std::vector<weight_vec> spring_strength(n, weight_vec(n));
474 std::vector<std::pair<weight_type, weight_type> > partial_derivatives(n);
477 kamada_kawai_spring_layout(
478 g, position, weight, edge_or_side_length, done, spring_constant, index,
480 spring_strength.begin(),
481 make_iterator_property_map(partial_derivatives.begin(), index,
482 std::pair<weight_type, weight_type>()));
488 template<typename Graph, typename PositionMap, typename WeightMap,
489 typename T, bool EdgeOrSideLength, typename Done>
491 kamada_kawai_spring_layout(
493 PositionMap position,
495 detail::graph::edge_or_side<EdgeOrSideLength, T> edge_or_side_length,
497 typename property_traits<WeightMap>::value_type spring_constant)
499 return kamada_kawai_spring_layout(g, position, weight, edge_or_side_length,
500 done, spring_constant,
501 get(vertex_index, g));
507 template<typename Graph, typename PositionMap, typename WeightMap,
508 typename T, bool EdgeOrSideLength, typename Done>
510 kamada_kawai_spring_layout(
512 PositionMap position,
514 detail::graph::edge_or_side<EdgeOrSideLength, T> edge_or_side_length,
517 typedef typename property_traits<WeightMap>::value_type weight_type;
518 return kamada_kawai_spring_layout(g, position, weight, edge_or_side_length,
519 done, weight_type(1));
525 template<typename Graph, typename PositionMap, typename WeightMap,
526 typename T, bool EdgeOrSideLength>
528 kamada_kawai_spring_layout(
530 PositionMap position,
532 detail::graph::edge_or_side<EdgeOrSideLength, T> edge_or_side_length)
534 typedef typename property_traits<WeightMap>::value_type weight_type;
535 return kamada_kawai_spring_layout(g, position, weight, edge_or_side_length,
536 layout_tolerance<weight_type>(),
538 get(vertex_index, g));
540 } // end namespace boost
542 #endif // BOOST_GRAPH_KAMADA_KAWAI_SPRING_LAYOUT_HPP