Graphviz 13.0.0~dev.20241220.2304
Loading...
Searching...
No Matches
sgd.c
Go to the documentation of this file.
1#include <assert.h>
2#include <limits.h>
3#include <neatogen/neato.h>
4#include <neatogen/sgd.h>
5#include <neatogen/dijkstra.h>
8#include <math.h>
9#include <stdlib.h>
10#include <util/alloc.h>
11#include <util/bitarray.h>
12
13static float calculate_stress(float *pos, term_sgd *terms, int n_terms) {
14 float stress = 0;
15 int ij;
16 for (ij=0; ij<n_terms; ij++) {
17 float dx = pos[2*terms[ij].i] - pos[2*terms[ij].j];
18 float dy = pos[2*terms[ij].i+1] - pos[2*terms[ij].j+1];
19 float r = hypotf(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 the swap is more expensive
25static void fisheryates_shuffle(term_sgd *terms, int n_terms, rk_state *rstate) {
26 int i;
27 for (i=n_terms-1; i>=1; i--) {
28 int j = rk_interval(i, rstate);
29
30 term_sgd temp = terms[i];
31 terms[i] = terms[j];
32 terms[j] = temp;
33 }
34}
35
36// graph_sgd data structure exists only to make dijkstras faster
37static graph_sgd * extract_adjacency(graph_t *G, int model) {
38 node_t *np;
39 edge_t *ep;
40 size_t n_nodes = 0, n_edges = 0;
41 for (np = agfstnode(G); np; np = agnxtnode(G,np)) {
42 assert(ND_id(np) == n_nodes);
43 n_nodes++;
44 for (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 (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 (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) ? aghead(ep) : 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 <= INT_MAX);
79 assert(n_edges == graph->sources[graph->n]);
80 graph->sources[n_nodes] = n_edges;
81
82 if (model == MODEL_SHORTPATH) {
83 // do nothing
84 } else if (model == MODEL_SUBSET) {
85 // i,j,k refer to actual node indices, while x,y refer to edge indices in graph->targets
86 // initialise to no neighbours
87 bitarray_t neighbours_i = bitarray_new(graph->n);
88 bitarray_t neighbours_j = bitarray_new(graph->n);
89 for (size_t i = 0; i < graph->n; i++) {
90 int deg_i = 0;
91 for (size_t x = graph->sources[i]; x < graph->sources[i + 1]; x++) {
92 size_t j = graph->targets[x];
93 if (!bitarray_get(neighbours_i, j)) { // ignore multiedges
94 bitarray_set(&neighbours_i, j, true); // set up sort of hashset
95 deg_i++;
96 }
97 }
98 for (size_t x = graph->sources[i]; x < graph->sources[i + 1]; x++) {
99 size_t j = graph->targets[x];
100 int intersect = 0;
101 int deg_j = 0;
102 for (size_t y = graph->sources[j]; y < graph->sources[j + 1];
103 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];
116 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 assert(false); // 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
142
143void sgd(graph_t *G, /* input graph */
144 int model /* distance model */)
145{
146 if (model == MODEL_CIRCUIT) {
147 agwarningf("circuit model not yet supported in Gmode=sgd, reverting to shortpath model\n");
148 model = MODEL_SHORTPATH;
149 }
150 if (model == MODEL_MDS) {
151 agwarningf("mds model not yet supported in Gmode=sgd, reverting to 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 i, n_fixed = 0, n_terms = 0;
162 for (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 (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 int ij;
186 for (ij=1; ij<n_terms; ij++) {
187 w_min = fminf(w_min, terms[ij].w);
188 w_max = fmaxf(w_max, terms[ij].w);
189 }
190 // note: Epsilon is different from MODE_KK and MODE_MAJOR as it is a minimum step size rather than energy threshold
191 // MaxIter is also different as it is a fixed number of iterations rather than a maximum
192 float eta_max = 1 / w_min;
193 float eta_min = Epsilon / w_max;
194 float 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 float *pos = gv_calloc(2 * n, sizeof(float));
200 bool *unfixed = gv_calloc(n, sizeof(bool));
201 for (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 int t;
214 rk_state rstate;
215 rk_seed(0, &rstate); // TODO: get seed from graph
216 for (t=0; t<MaxIter; t++) {
217 fisheryates_shuffle(terms, n_terms, &rstate);
218 float eta = eta_max * exp(-lambda * t);
219 for (ij=0; ij<n_terms; ij++) {
220 // cap step size
221 float mu = eta * terms[ij].w;
222 if (mu > 1)
223 mu = 1;
224
225 float dx = pos[2*terms[ij].i] - pos[2*terms[ij].j];
226 float dy = pos[2*terms[ij].i+1] - pos[2*terms[ij].j+1];
227 float mag = hypotf(dx, dy);
228
229 float r = (mu * (mag-terms[ij].d)) / (2*mag);
230 float r_x = r * dx;
231 float r_y = r * dy;
232
233 if (unfixed[terms[ij].i]) {
234 pos[2*terms[ij].i] -= r_x;
235 pos[2*terms[ij].i+1] -= r_y;
236 }
237 if (unfixed[terms[ij].j]) {
238 pos[2*terms[ij].j] += r_x;
239 pos[2*terms[ij].j+1] += r_y;
240 }
241 }
242 if (Verbose) {
243 fprintf(stderr, " %.3f", calculate_stress(pos, terms, n_terms));
244 }
245 }
246 if (Verbose) {
247 fprintf(stderr, "\nfinished in %.2f sec\n", elapsed_sec());
248 }
249 free(terms);
250
251 // copy temporary positions back into graph_t
252 for (i=0; i<n; i++) {
254 ND_pos(node)[0] = pos[2*i];
255 ND_pos(node)[1] = pos[2*i+1];
256 }
257 free(pos);
258 free(unfixed);
259}
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:60
static bool Verbose
Definition gml2gv.c:23
void free(void *)
int agnnodes(Agraph_t *g)
Definition graph.c:169
#define ED_dist(e)
Definition types.h:602
#define agtail(e)
Definition cgraph.h:880
Agedge_t * agnxtedge(Agraph_t *g, Agedge_t *e, Agnode_t *n)
Definition edge.c:94
#define aghead(e)
Definition cgraph.h:881
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:333
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:134
static void fisheryates_shuffle(term_sgd *terms, int n_terms, rk_state *rstate)
Definition sgd.c:25
static float calculate_stress(float *pos, term_sgd *terms, int n_terms)
Definition sgd.c:13
void sgd(graph_t *G, int model)
Definition sgd.c:143
static graph_sgd * extract_adjacency(graph_t *G, int model)
Definition sgd.c:37
graph or subgraph
Definition cgraph.h:425
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
static bool intersect(Ppoint_t a, Ppoint_t b, Ppoint_t c, Ppoint_t d)
Definition visibility.c:78