Graphviz 13.0.0~dev.20250210.0415
Loading...
Searching...
No Matches
sgd.c
Go to the documentation of this file.
1#include <assert.h>
2#include <limits.h>
3#include <math.h>
4#include <neatogen/dijkstra.h>
5#include <neatogen/neato.h>
8#include <neatogen/sgd.h>
9#include <stdlib.h>
10#include <util/alloc.h>
11#include <util/bitarray.h>
12#include <util/unreachable.h>
13
14static double calculate_stress(double *pos, term_sgd *terms, int n_terms) {
15 double stress = 0;
16 for (int ij = 0; ij < n_terms; ij++) {
17 const double dx = pos[2 * terms[ij].i] - pos[2 * terms[ij].j];
18 const double dy = pos[2 * terms[ij].i + 1] - pos[2 * terms[ij].j + 1];
19 const double r = hypot(dx, dy) - terms[ij].d;
20 stress += terms[ij].w * (r * r);
21 }
22 return stress;
23}
24// it is much faster to shuffle term rather than pointers to term, even though
25// the swap is more expensive
26static void fisheryates_shuffle(term_sgd *terms, int n_terms,
27 rk_state *rstate) {
28 for (int i = n_terms - 1; i >= 1; i--) {
29 int j = rk_interval(i, rstate);
30
31 term_sgd temp = terms[i];
32 terms[i] = terms[j];
33 terms[j] = temp;
34 }
35}
36
37// graph_sgd data structure exists only to make dijkstras faster
38static graph_sgd *extract_adjacency(graph_t *G, int model) {
39 size_t n_nodes = 0, n_edges = 0;
40 for (node_t *np = agfstnode(G); np; np = agnxtnode(G, np)) {
41 assert(ND_id(np) == n_nodes);
42 n_nodes++;
43 for (edge_t *ep = agfstedge(G, np); ep; ep = agnxtedge(G, ep, np)) {
44 if (agtail(ep) != aghead(ep)) { // ignore self-loops and double edges
45 n_edges++;
46 }
47 }
48 }
50 graph->sources = gv_calloc(n_nodes + 1, sizeof(size_t));
51 graph->pinneds = bitarray_new(n_nodes);
52 graph->targets = gv_calloc(n_edges, sizeof(size_t));
53 graph->weights = gv_calloc(n_edges, sizeof(float));
54
55 graph->n = n_nodes;
56 assert(n_edges <= INT_MAX);
57 graph->sources[graph->n] = n_edges; // to make looping nice
58
59 n_nodes = 0, n_edges = 0;
60 for (node_t *np = agfstnode(G); np; np = agnxtnode(G, np)) {
61 assert(n_edges <= INT_MAX);
62 graph->sources[n_nodes] = n_edges;
63 bitarray_set(&graph->pinneds, n_nodes, isFixed(np));
64 for (edge_t *ep = agfstedge(G, np); ep; ep = agnxtedge(G, ep, np)) {
65 if (agtail(ep) == aghead(ep)) { // ignore self-loops and double edges
66 continue;
67 }
68 node_t *target = (agtail(ep) == np)
69 ? aghead(ep)
70 : agtail(ep); // in case edge is reversed
71 graph->targets[n_edges] = (size_t)ND_id(target);
72 graph->weights[n_edges] = ED_dist(ep);
73 assert(graph->weights[n_edges] > 0);
74 n_edges++;
75 }
76 n_nodes++;
77 }
78 assert(n_nodes == graph->n);
79 assert(n_edges <= INT_MAX);
80 assert(n_edges == graph->sources[graph->n]);
81 graph->sources[n_nodes] = n_edges;
82
83 if (model == MODEL_SHORTPATH) {
84 // do nothing
85 } else if (model == MODEL_SUBSET) {
86 // i,j,k refer to actual node indices, while x,y refer to edge indices in
87 // graph->targets initialise to no neighbours
88 bitarray_t neighbours_i = bitarray_new(graph->n);
89 bitarray_t neighbours_j = bitarray_new(graph->n);
90 for (size_t i = 0; i < graph->n; i++) {
91 int deg_i = 0;
92 for (size_t x = graph->sources[i]; x < graph->sources[i + 1]; x++) {
93 size_t j = graph->targets[x];
94 if (!bitarray_get(neighbours_i, j)) { // ignore multiedges
95 bitarray_set(&neighbours_i, j, true); // set up sort of hashset
96 deg_i++;
97 }
98 }
99 for (size_t x = graph->sources[i]; x < graph->sources[i + 1]; x++) {
100 size_t j = graph->targets[x];
101 int intersect = 0;
102 int deg_j = 0;
103 for (size_t y = graph->sources[j]; y < graph->sources[j + 1]; y++) {
104 size_t k = graph->targets[y];
105 if (!bitarray_get(neighbours_j, k)) { // ignore multiedges
106 bitarray_set(&neighbours_j, k, true); // set up sort of hashset
107 deg_j++;
108 if (bitarray_get(neighbours_i, k)) {
109 intersect++;
110 }
111 }
112 }
113 graph->weights[x] = deg_i + deg_j - (2 * intersect);
114 assert(graph->weights[x] > 0);
115 for (size_t y = graph->sources[j]; y < graph->sources[j + 1]; y++) {
116 size_t k = graph->targets[y];
117 bitarray_set(&neighbours_j, k, false); // reset sort of hashset
118 }
119 }
120 for (size_t x = graph->sources[i]; x < graph->sources[i + 1]; x++) {
121 size_t j = graph->targets[x];
122 bitarray_set(&neighbours_i, j, false); // reset sort of hashset
123 }
124 }
125 bitarray_reset(&neighbours_i);
126 bitarray_reset(&neighbours_j);
127 } else {
128 // TODO: model == MODEL_MDS and MODEL_CIRCUIT
129 UNREACHABLE(); // mds and circuit model not supported
130 }
131 return graph;
132}
134 free(graph->sources);
135 bitarray_reset(&graph->pinneds);
136 free(graph->targets);
137 free(graph->weights);
138 free(graph);
139}
140
141void sgd(graph_t *G, /* input graph */
142 int model /* distance model */) {
143 if (model == MODEL_CIRCUIT) {
144 agwarningf("circuit model not yet supported in Gmode=sgd, reverting to "
145 "shortpath model\n");
146 model = MODEL_SHORTPATH;
147 }
148 if (model == MODEL_MDS) {
149 agwarningf("mds model not yet supported in Gmode=sgd, reverting to "
150 "shortpath model\n");
151 model = MODEL_SHORTPATH;
152 }
153 int n = agnnodes(G);
154
155 if (Verbose) {
156 fprintf(stderr, "calculating shortest paths and setting up stress terms:");
157 start_timer();
158 }
159 // calculate how many terms will be needed as fixed nodes can be ignored
160 int n_fixed = 0, n_terms = 0;
161 for (int i = 0; i < n; i++) {
162 if (!isFixed(GD_neato_nlist(G)[i])) {
163 n_fixed++;
164 n_terms += n - n_fixed;
165 }
166 }
167 term_sgd *terms = gv_calloc(n_terms, sizeof(term_sgd));
168 // calculate term values through shortest paths
169 int offset = 0;
171 for (int i = 0; i < n; i++) {
172 if (!isFixed(GD_neato_nlist(G)[i])) {
173 offset += dijkstra_sgd(graph, i, terms + offset);
174 }
175 }
176 assert(offset == n_terms);
178 if (Verbose) {
179 fprintf(stderr, " %.2f sec\n", elapsed_sec());
180 }
181
182 // initialise annealing schedule
183 float w_min = terms[0].w, w_max = terms[0].w;
184 for (int ij = 1; ij < n_terms; ij++) {
185 w_min = fminf(w_min, terms[ij].w);
186 w_max = fmaxf(w_max, terms[ij].w);
187 }
188 // note: Epsilon is different from MODE_KK and MODE_MAJOR as it is a minimum
189 // step size rather than energy threshold
190 // MaxIter is also different as it is a fixed number of iterations
191 // rather than a maximum
192 const double eta_max = 1.0 / w_min;
193 const double eta_min = Epsilon / w_max;
194 const double lambda = log(eta_max / eta_min) / (MaxIter - 1);
195
196 // initialise starting positions (from neatoprocs)
198 // copy initial positions and state into temporary space for speed
199 double *const pos = gv_calloc(2 * n, sizeof(double));
200 bool *unfixed = gv_calloc(n, sizeof(bool));
201 for (int i = 0; i < n; i++) {
203 pos[2 * i] = ND_pos(node)[0];
204 pos[2 * i + 1] = ND_pos(node)[1];
205 unfixed[i] = !isFixed(node);
206 }
207
208 // perform optimisation
209 if (Verbose) {
210 fprintf(stderr, "solving model:");
211 start_timer();
212 }
213 rk_state rstate;
214 rk_seed(0, &rstate); // TODO: get seed from graph
215 for (int t = 0; t < MaxIter; t++) {
216 fisheryates_shuffle(terms, n_terms, &rstate);
217 const double eta = eta_max * exp(-lambda * t);
218 for (int ij = 0; ij < n_terms; ij++) {
219 // cap step size
220 const double mu = fmin(eta * terms[ij].w, 1);
221
222 const double dx = pos[2 * terms[ij].i] - pos[2 * terms[ij].j];
223 const double dy = pos[2 * terms[ij].i + 1] - pos[2 * terms[ij].j + 1];
224 const double mag = hypot(dx, dy);
225
226 const double r = (mu * (mag - terms[ij].d)) / (2 * mag);
227 const double r_x = r * dx;
228 const double r_y = r * dy;
229
230 if (unfixed[terms[ij].i]) {
231 pos[2 * terms[ij].i] -= r_x;
232 pos[2 * terms[ij].i + 1] -= r_y;
233 }
234 if (unfixed[terms[ij].j]) {
235 pos[2 * terms[ij].j] += r_x;
236 pos[2 * terms[ij].j + 1] += r_y;
237 }
238 }
239 if (Verbose) {
240 fprintf(stderr, " %.3f", calculate_stress(pos, terms, n_terms));
241 }
242 }
243 if (Verbose) {
244 fprintf(stderr, "\nfinished in %.2f sec\n", elapsed_sec());
245 }
246 free(terms);
247
248 // copy temporary positions back into graph_t
249 for (int i = 0; i < n; i++) {
251 ND_pos(node)[0] = pos[2 * i];
252 ND_pos(node)[1] = pos[2 * i + 1];
253 }
254 free(pos);
255 free(unfixed);
256}
Memory allocation wrappers that exit on failure.
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
static void * gv_alloc(size_t size)
Definition alloc.h:47
#define Epsilon
Definition arcball.h:210
API for compacted arrays of booleans.
static bitarray_t bitarray_new(size_t size_bits)
create an array of the given element length
Definition bitarray.h:46
static bool bitarray_get(bitarray_t self, size_t index)
get the value of the given element
Definition bitarray.h:64
static void bitarray_set(bitarray_t *self, size_t index, bool value)
set or clear the value of the given element
Definition bitarray.h:79
static void bitarray_reset(bitarray_t *self)
free underlying resources and leave a bit array empty
Definition bitarray.h:98
static float dy
Definition draw.c:38
static float dx
Definition draw.c:37
#define G
Definition gdefs.h:7
int MaxIter
Definition globals.h:61
static bool Verbose
Definition gml2gv.c:23
void free(void *)
int agnnodes(Agraph_t *g)
Definition graph.c:159
#define ED_dist(e)
Definition types.h:602
#define agtail(e)
Definition cgraph.h:883
Agedge_t * agnxtedge(Agraph_t *g, Agedge_t *e, Agnode_t *n)
Definition edge.c:94
#define aghead(e)
Definition cgraph.h:884
Agedge_t * agfstedge(Agraph_t *g, Agnode_t *n)
Definition edge.c:85
void agwarningf(const char *fmt,...)
Definition agerror.c:173
#define GD_neato_nlist(g)
Definition types.h:392
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:47
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:40
#define ND_pos(n)
Definition types.h:520
Agraph_t * graph(char *name)
Definition gv.cpp:30
swig_ptr_object_handlers offset
Definition gv_php.cpp:5907
int dijkstra_sgd(graph_sgd *graph, int source, term_sgd *terms)
Definition dijkstra.c:292
#define isFixed(n)
Definition macros.h:19
#define mu
Definition maze.c:134
#define ND_id(n)
Definition mm2gv.c:39
#define MODEL_SUBSET
Definition neato.h:17
#define MODEL_MDS
Definition neato.h:18
#define MODEL_CIRCUIT
Definition neato.h:16
#define MODEL_SHORTPATH
Definition neato.h:15
NEATOPROCS_API void initial_positions(graph_t *, int)
Definition stuff.c:334
unsigned long rk_interval(unsigned long max, rk_state *state)
Definition randomkit.c:138
void rk_seed(unsigned long seed, rk_state *state)
Definition randomkit.c:75
static void free_adjacency(graph_sgd *graph)
Definition sgd.c:133
static void fisheryates_shuffle(term_sgd *terms, int n_terms, rk_state *rstate)
Definition sgd.c:26
static double calculate_stress(double *pos, term_sgd *terms, int n_terms)
Definition sgd.c:14
void sgd(graph_t *G, int model)
Definition sgd.c:141
static graph_sgd * extract_adjacency(graph_t *G, int model)
Definition sgd.c:38
graph or subgraph
Definition cgraph.h:424
Definition sgd.h:10
int j
Definition sgd.h:11
float w
Definition sgd.h:12
int i
Definition sgd.h:11
double elapsed_sec(void)
Definition timing.c:48
void start_timer(void)
Definition timing.c:43
#define UNREACHABLE()
Definition unreachable.h:30
static bool intersect(Ppoint_t a, Ppoint_t b, Ppoint_t c, Ppoint_t d)
Definition visibility.c:78