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