|
1 | 1 | // Copyright 2004 The Trustees of Indiana University. |
| 2 | +// Copyright (c) 2026 Arnaud Becheler |
2 | 3 |
|
3 | 4 | // Use, modification and distribution is subject to the Boost Software |
4 | 5 | // License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at |
|
7 | 8 | // Authors: Jeremiah Willcock |
8 | 9 | // Douglas Gregor |
9 | 10 | // Andrew Lumsdaine |
10 | | -#include <boost/graph/gursoy_atun_layout.hpp> |
11 | | -#include "boost/graph/adjacency_list.hpp" |
12 | | -#include "boost/graph/random.hpp" |
13 | | -#include "boost/graph/graphviz.hpp" |
14 | | -#include "boost/random/mersenne_twister.hpp" |
15 | | -#include "boost/random/linear_congruential.hpp" |
16 | | -#include "boost/random/uniform_01.hpp" |
17 | | -#include <iostream> |
18 | | -#include <fstream> |
19 | | -#include <sstream> |
20 | | - |
21 | | -#if 0 |
22 | | -#include <boost/graph/plod_generator.hpp> |
23 | | -#include <boost/graph/small_world_generator.hpp> |
24 | | -#endif |
25 | | -using namespace boost; |
26 | | - |
27 | | -template < class Property, class Vertex > struct position_writer |
28 | | -{ |
29 | | - const Property& property; |
30 | | - |
31 | | - position_writer(const Property& property) : property(property) {} |
32 | | - |
33 | | - void operator()(std::ostream& os, const Vertex& v) const |
34 | | - { |
35 | | - os << "[pos=\"" << int(property[v][0]) << "," << int(property[v][1]) |
36 | | - << "\"]"; |
37 | | - } |
38 | | -}; |
39 | 11 |
|
40 | | -struct graph_writer |
41 | | -{ |
42 | | - void operator()(std::ostream& os) const |
43 | | - { |
44 | | - os << "node [shape=point, width=\".01\", height=\".01\", " |
45 | | - "fixedsize=\"true\"]" |
46 | | - << std::endl; |
47 | | - } |
48 | | -}; |
49 | | - |
50 | | -int main(int, char*[]) |
| 12 | +#include <boost/graph/gursoy_atun_layout.hpp> |
| 13 | +#include <boost/graph/adjacency_list.hpp> |
| 14 | +#include <boost/core/lightweight_test.hpp> |
| 15 | + |
| 16 | +#include <algorithm> |
| 17 | +#include <cmath> |
| 18 | +#include <map> |
| 19 | +#include <random> |
| 20 | +#include <vector> |
| 21 | + |
| 22 | +using graph_type = boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS, boost::no_property, boost::property< boost::edge_weight_t, double > >; |
| 23 | +using vertex_index_map = boost::property_map< graph_type, boost::vertex_index_t >::type; |
| 24 | +using topology = boost::heart_topology<>; |
| 25 | +using point = topology::point_type; |
| 26 | +using position_map = boost::iterator_property_map< std::vector< point >::iterator, vertex_index_map, point, point& >; |
| 27 | + |
| 28 | +// Builds the 20x20 weighted grid Gursoy and Atun used |
| 29 | +void build_grid(graph_type& g) |
51 | 30 | { |
52 | | - // Generate a graph structured like a grid, cylinder, or torus; lay it out |
53 | | - // in a square grid; and output it in dot format |
54 | | - |
55 | | - typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::undirectedS, |
56 | | - boost::no_property, boost::property< boost::edge_weight_t, double > > |
57 | | - graph_type; |
58 | | - typedef boost::graph_traits< graph_type >::vertex_descriptor |
59 | | - vertex_descriptor; |
60 | | - // boost::mt19937 rng; |
61 | | - // boost::generate_random_graph(graph, 100, 600, rng, false, false); |
62 | | - |
63 | | -#if 1 |
64 | | - graph_type graph; |
65 | | - |
66 | | - // Make grid, like Gursoy and Atun used |
| 31 | + using vertex_descriptor = boost::graph_traits< graph_type >::vertex_descriptor; |
| 32 | + constexpr int grid_size = 20; |
67 | 33 | std::map< int, std::map< int, vertex_descriptor > > verts; |
68 | | - const int grid_size = 20; |
69 | | - boost::minstd_rand edge_weight_gen(42); |
70 | | - boost::uniform_01< boost::minstd_rand > random_edge_weight(edge_weight_gen); |
| 34 | + std::mt19937 engine(42); |
| 35 | + std::uniform_real_distribution< double > dist(0.0, 1.0); |
| 36 | + auto random_edge_weight = [&] { return dist(engine); }; |
| 37 | + |
71 | 38 | for (int i = 0; i < grid_size; ++i) |
72 | 39 | for (int j = 0; j < grid_size; ++j) |
73 | | - verts[i][j] = add_vertex(graph); |
| 40 | + verts[i][j] = boost::add_vertex(g); |
| 41 | + |
74 | 42 | for (int i = 0; i < grid_size; ++i) |
75 | 43 | { |
76 | 44 | for (int j = 0; j < grid_size; ++j) |
77 | 45 | { |
78 | 46 | if (i != 0) |
79 | | - add_edge( |
80 | | - verts[i][j], verts[i - 1][j], random_edge_weight(), graph); |
| 47 | + boost::add_edge(verts[i][j], verts[i - 1][j], random_edge_weight(), g); |
81 | 48 | if (j != 0) |
82 | | - add_edge( |
83 | | - verts[i][j], verts[i][j - 1], random_edge_weight(), graph); |
84 | | -#if 0 |
85 | | - // Uncomment parts of this to get a cylinder or torus |
86 | | - if (i == 0) |
87 | | - add_edge(verts[0][j], verts[grid_size-1][j], random_edge_weight(), |
88 | | - graph); |
89 | | - if (j == 0) |
90 | | - add_edge(verts[i][0], verts[i][grid_size-1], random_edge_weight(), |
91 | | - graph); |
92 | | -#endif |
| 49 | + boost::add_edge(verts[i][j], verts[i][j - 1], random_edge_weight(), g); |
93 | 50 | } |
94 | 51 | } |
95 | | -#else |
96 | | - using namespace boost; |
| 52 | +} |
97 | 53 |
|
98 | | -#if 0 |
99 | | - int n = 10000; |
100 | | - double alpha = 0.4; |
101 | | - double beta = 50; |
102 | | - minstd_rand gen(42); |
103 | | - graph_type graph(plod_iterator<minstd_rand, graph_type>(gen, n, alpha, beta), |
104 | | - plod_iterator<minstd_rand, graph_type>(), |
105 | | - n); |
106 | | -#else |
107 | | - int n = 1000; |
108 | | - int k = 6; |
109 | | - double p = 0.001; |
110 | | - minstd_rand gen(42); |
111 | | - graph_type graph(small_world_iterator< minstd_rand >(gen, n, k, p), |
112 | | - small_world_iterator< minstd_rand >(n, k), n); |
113 | | -#endif |
114 | | -#endif |
115 | | - // boost::read_graphviz(stdin, graph); |
| 54 | +// Positions are heuristic, so we assert invariants instead of exact coordinates |
| 55 | +// every point finite, inside the bounds, not all collapsed in one spot. |
| 56 | +bool layout_is_valid(const std::vector< point >& pos) |
| 57 | +{ |
| 58 | + constexpr double eps = 1e-6; |
| 59 | + constexpr double y_min = -2000.0; |
| 60 | + // The heart's two circles bulge past the square to x = +-500(1 + sqrt2) |
| 61 | + // and up to y = 500(sqrt2 - 1) and the square's tip reaches y = -2000. |
| 62 | + const double x_max = 500.0 * (1.0 + std::sqrt(2.0)); |
| 63 | + const double x_min = -x_max; |
| 64 | + const double y_max = 500.0 * (std::sqrt(2.0) - 1.0); |
| 65 | + double min_x = pos[0][0], max_x = pos[0][0], min_y = pos[0][1], max_y = pos[0][1]; |
| 66 | + |
| 67 | + for (const point& p : pos) |
| 68 | + { |
| 69 | + if (!std::isfinite(p[0]) || !std::isfinite(p[1])) |
| 70 | + return false; |
| 71 | + if (p[0] < x_min - eps || p[0] > x_max + eps) |
| 72 | + return false; |
| 73 | + if (p[1] < y_min - eps || p[1] > y_max + eps) |
| 74 | + return false; |
| 75 | + min_x = std::min(min_x, p[0]); |
| 76 | + max_x = std::max(max_x, p[0]); |
| 77 | + min_y = std::min(min_y, p[1]); |
| 78 | + max_y = std::max(max_y, p[1]); |
| 79 | + } |
| 80 | + constexpr double spread_threshold = 1.0; |
| 81 | + return (max_x - min_x) > spread_threshold || (max_y - min_y) > spread_threshold; |
| 82 | +} |
116 | 83 |
|
117 | | - typedef boost::property_map< graph_type, boost::vertex_index_t >::type |
118 | | - VertexIndexMap; |
119 | | - VertexIndexMap vertex_index = get(boost::vertex_index_t(), graph); |
| 84 | +int main() |
| 85 | +{ |
| 86 | + graph_type g; |
| 87 | + build_grid(g); |
120 | 88 |
|
121 | | - typedef boost::heart_topology<> topology; |
| 89 | + auto index_map = boost::get(boost::vertex_index, g); |
| 90 | + std::vector< point > positions(boost::num_vertices(g)); |
| 91 | + position_map position(positions.begin(), index_map); |
122 | 92 | topology space; |
123 | 93 |
|
124 | | - typedef topology::point_type point; |
125 | | - std::vector< point > position_vector(num_vertices(graph)); |
126 | | - typedef boost::iterator_property_map< std::vector< point >::iterator, |
127 | | - VertexIndexMap, point, point& > |
128 | | - Position; |
129 | | - Position position(position_vector.begin(), vertex_index); |
130 | | - |
131 | | - boost::gursoy_atun_layout(graph, space, position); |
132 | | - |
133 | | -#if 0 |
134 | | - std::cerr << "--------Unweighted layout--------\n"; |
135 | | - boost::write_graphviz(std::cout, graph, |
136 | | - position_writer<Position, vertex_descriptor>(position), |
137 | | - boost::default_writer(), |
138 | | - graph_writer()); |
139 | | -#endif |
140 | | - |
141 | | - boost::gursoy_atun_layout( |
142 | | - graph, space, position, weight_map(get(boost::edge_weight, graph))); |
143 | | - |
144 | | -#if 0 |
145 | | - std::cerr << "--------Weighted layout--------\n"; |
146 | | - boost::write_graphviz(std::cout, graph, |
147 | | - position_writer<Position, vertex_descriptor>(position), |
148 | | - boost::default_writer(), |
149 | | - graph_writer()); |
150 | | -#endif |
151 | | - return 0; |
| 94 | + // unweighted layout uses BFS hop distances |
| 95 | + boost::gursoy_atun_layout(g, space, position); |
| 96 | + BOOST_TEST(layout_is_valid(positions)); |
| 97 | + |
| 98 | + // weighted layout uses Dijkstra distances over the edge weights |
| 99 | + auto edge_weights = boost::get(boost::edge_weight, g); |
| 100 | + const int nsteps = static_cast< int >(boost::num_vertices(g)); |
| 101 | + const double diameter_initial = std::sqrt(static_cast< double >(boost::num_vertices(g))); |
| 102 | + const double diameter_final = 1.0; |
| 103 | + const double learning_initial = 0.8; |
| 104 | + const double learning_final = 0.2; |
| 105 | + boost::gursoy_atun_layout(g, space, position, nsteps, diameter_initial, diameter_final, learning_initial, learning_final, index_map, edge_weights); |
| 106 | + BOOST_TEST(layout_is_valid(positions)); |
| 107 | + |
| 108 | + return boost::report_errors(); |
152 | 109 | } |
0 commit comments