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