Graphviz 16.1.0~dev.20260826.0140
Loading...
Searching...
No Matches
sgd.c
Go to the documentation of this file.
1#include "config.h"
2
3#include <assert.h>
4#include <cgraph/cgraph.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, size_t n_terms) {
18 double stress = 0;
19 for (size_t 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, size_t n_terms,
30 rk_state *rstate) {
31 for (size_t i = n_terms - 1; n_terms > 0 && i >= 1; i--) {
32 const unsigned long 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) >= 0 && (size_t)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 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 graph->sources[n_nodes] = n_edges;
62 bitarray_set(&graph->pinneds, n_nodes, isFixed(np));
63 for (edge_t *ep = agfstedge(G, np); ep; ep = agnxtedge(G, ep, np)) {
64 if (agtail(ep) == aghead(ep)) { // ignore self-loops and double edges
65 continue;
66 }
67 node_t *target = (agtail(ep) == np)
68 ? aghead(ep)
69 : agtail(ep); // in case edge is reversed
70 graph->targets[n_edges] = (size_t)ND_id(target);
71 graph->weights[n_edges] = ED_dist(ep);
72 assert(graph->weights[n_edges] > 0);
73 n_edges++;
74 }
75 n_nodes++;
76 }
77 assert(n_nodes == graph->n);
78 assert(n_edges == graph->sources[graph->n]);
79 graph->sources[n_nodes] = n_edges;
80
81 if (model == MODEL_SHORTPATH) {
82 // do nothing
83 } else if (model == MODEL_SUBSET) {
84 // i,j,k refer to actual node indices, while x,y refer to edge indices in
85 // graph->targets initialise to no neighbours
86 bitarray_t neighbours_i = bitarray_new(graph->n);
87 bitarray_t neighbours_j = bitarray_new(graph->n);
88 for (size_t i = 0; i < graph->n; i++) {
89 int deg_i = 0;
90 for (size_t x = graph->sources[i]; x < graph->sources[i + 1]; x++) {
91 size_t j = graph->targets[x];
92 if (!bitarray_get(neighbours_i, j)) { // ignore multiedges
93 bitarray_set(&neighbours_i, j, true); // set up sort of hashset
94 deg_i++;
95 }
96 }
97 for (size_t x = graph->sources[i]; x < graph->sources[i + 1]; x++) {
98 size_t j = graph->targets[x];
99 int intersect = 0;
100 int deg_j = 0;
101 for (size_t y = graph->sources[j]; y < graph->sources[j + 1]; y++) {
102 size_t k = graph->targets[y];
103 if (!bitarray_get(neighbours_j, k)) { // ignore multiedges
104 bitarray_set(&neighbours_j, k, true); // set up sort of hashset
105 deg_j++;
106 if (bitarray_get(neighbours_i, k)) {
107 intersect++;
108 }
109 }
110 }
111 graph->weights[x] = deg_i + deg_j - (2 * intersect);
112 assert(graph->weights[x] > 0);
113 for (size_t y = graph->sources[j]; y < graph->sources[j + 1]; y++) {
114 size_t k = graph->targets[y];
115 bitarray_set(&neighbours_j, k, false); // reset sort of hashset
116 }
117 }
118 for (size_t x = graph->sources[i]; x < graph->sources[i + 1]; x++) {
119 size_t j = graph->targets[x];
120 bitarray_set(&neighbours_i, j, false); // reset sort of hashset
121 }
122 }
123 bitarray_reset(&neighbours_i);
124 bitarray_reset(&neighbours_j);
125 } else {
126 // TODO: model == MODEL_MDS and MODEL_CIRCUIT
127 UNREACHABLE(); // mds and circuit model not supported
128 }
129 return graph;
130}
132 free(graph->sources);
133 bitarray_reset(&graph->pinneds);
134 free(graph->targets);
135 free(graph->weights);
136 free(graph);
137}
138
139void sgd(graph_t *G, /* input graph */
140 int model /* distance model */) {
141 if (model == MODEL_CIRCUIT) {
142 agwarningf("circuit model not yet supported in Gmode=sgd, reverting to "
143 "shortpath model\n");
144 model = MODEL_SHORTPATH;
145 }
146 if (model == MODEL_MDS) {
147 agwarningf("mds model not yet supported in Gmode=sgd, reverting to "
148 "shortpath model\n");
149 model = MODEL_SHORTPATH;
150 }
151 const size_t n = agnnodes_z(G);
152
153 if (Verbose) {
154 fprintf(stderr, "calculating shortest paths and setting up stress terms:");
155 start_timer();
156 }
157 // calculate how many terms will be needed as fixed nodes can be ignored
158 size_t n_fixed = 0;
159 size_t n_terms = 0;
160 for (size_t i = 0; i < n; i++) {
161 if (!isFixed(GD_neato_nlist(G)[i])) {
162 n_fixed++;
163 n_terms += n - n_fixed;
164 }
165 }
166 term_sgd *terms = gv_calloc(n_terms, sizeof(term_sgd));
167 // calculate term values through shortest paths
168 size_t offset = 0;
170 for (size_t i = 0; i < n; i++) {
171 if (!isFixed(GD_neato_nlist(G)[i])) {
172 offset += dijkstra_sgd(graph, i, terms + offset);
173 }
174 }
175 assert(offset == n_terms);
177 if (Verbose) {
178 fprintf(stderr, " %.2f sec\n", elapsed_sec());
179 }
180
181 // initialise annealing schedule
182 float w_min = terms[0].w, w_max = terms[0].w;
183 for (size_t ij = 1; ij < n_terms; ij++) {
184 w_min = fminf(w_min, terms[ij].w);
185 w_max = fmaxf(w_max, terms[ij].w);
186 }
187 // note: Epsilon is different from MODE_KK and MODE_MAJOR as it is a minimum
188 // step size rather than energy threshold
189 // MaxIter is also different as it is a fixed number of iterations
190 // rather than a maximum
191 const double eta_max = 1.0 / w_min;
192 const double eta_min = Epsilon / w_max;
193 const double lambda = log(eta_max / eta_min) / (MaxIter - 1);
194
195 // initialise starting positions (from neatoprocs)
196 initial_positions(G, (int)n);
197 // copy initial positions and state into temporary space for speed
198 double *const pos = gv_calloc(2 * n, sizeof(double));
199 bool *unfixed = gv_calloc(n, sizeof(bool));
200 for (size_t i = 0; i < n; i++) {
202 pos[2 * i] = ND_pos(node)[0];
203 pos[2 * i + 1] = ND_pos(node)[1];
204 unfixed[i] = !isFixed(node);
205 }
206
207 // perform optimisation
208 if (Verbose) {
209 fprintf(stderr, "solving model:");
210 start_timer();
211 }
212 rk_state rstate;
213 rk_seed(0, &rstate); // TODO: get seed from graph
214 for (int t = 0; t < MaxIter; t++) {
215 fisheryates_shuffle(terms, n_terms, &rstate);
216 const double eta = eta_max * exp(-lambda * t);
217 for (size_t ij = 0; ij < n_terms; ij++) {
218 // cap step size
219 const double mu = fmin(eta * terms[ij].w, 1);
220
221 const double dx = pos[2 * terms[ij].i] - pos[2 * terms[ij].j];
222 const double dy = pos[2 * terms[ij].i + 1] - pos[2 * terms[ij].j + 1];
223 const double mag = hypot(dx, dy);
224
225 const double r = (mu * (mag - terms[ij].d)) / (2 * mag);
226 const double r_x = r * dx;
227 const double r_y = r * dy;
228
229 if (unfixed[terms[ij].i]) {
230 pos[2 * terms[ij].i] -= r_x;
231 pos[2 * terms[ij].i + 1] -= r_y;
232 }
233 if (unfixed[terms[ij].j]) {
234 pos[2 * terms[ij].j] += r_x;
235 pos[2 * terms[ij].j + 1] += r_y;
236 }
237 }
238 if (Verbose) {
239 fprintf(stderr, " %.3f", calculate_stress(pos, terms, n_terms));
240 }
241 }
242 if (Verbose) {
243 fprintf(stderr, "\nfinished in %.2f sec\n", elapsed_sec());
244 }
245 free(terms);
246
247 // copy temporary positions back into graph_t
248 for (size_t i = 0; i < n; i++) {
250 ND_pos(node)[0] = pos[2 * i];
251 ND_pos(node)[1] = pos[2 * i + 1];
252 }
253 free(pos);
254 free(unfixed);
255}
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:137
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
abstract graph C library, Cgraph API
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:64
static bool Verbose
Definition gml2gv.c:26
void free(void *)
size_t agnnodes_z(const Agraph_t *g)
Definition graph.c:161
#define ED_dist(e)
Definition types.h:602
#define agtail(e)
Definition cgraph.h:982
Agedge_t * agnxtedge(Agraph_t *g, Agedge_t *e, Agnode_t *n)
Definition edge.c:98
#define aghead(e)
Definition cgraph.h:983
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:137
size_t dijkstra_sgd(graph_sgd *graph, size_t source, term_sgd *terms)
Definition dijkstra.c:300
#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:131
static double calculate_stress(double *pos, term_sgd *terms, size_t n_terms)
Definition sgd.c:17
void sgd(graph_t *G, int model)
Definition sgd.c:139
static void fisheryates_shuffle(term_sgd *terms, size_t n_terms, rk_state *rstate)
Definition sgd.c:29
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