chiark / gitweb /
starting to make it compile
[moebius2.git] / energy.c
1 /*
2  * We try to find an optimal triangle grid
3  */
4
5 #include "common.h"
6 #include "bgl.h"
7 #include "mgraph.h"
8
9 #define BEST_F "best"
10 #define INITIAL_F "initial"
11
12 static double edgewise_vertex_displacement_cost(const Vertices vertices);
13 static double noncircular_rim_cost(Vertices vertices);
14
15 static void compute_vertex_areas(const Vertices vertices, double areas[N]);
16 static double best_energy= DBL_MAX;
17 static void flushoutput(void);
18
19 static void cost(double *energy, double tweight, double tcost);
20 #define COST(weight, compute) cost(&energy, (weight), (compute))
21
22 /*---------- main energy computation and subroutines ----------*/
23
24 static double compute_energy(Vertices vertices) {
25   double vertex_areas[N], energy;
26
27   compute_vertex_areas(vertices,vertex_areas);
28   energy= 0;
29   printf("cost > energy |");
30
31   COST(1000.0, edgewise_vertex_displacement_cost(vertices));
32   COST(1.0,    graph_layout_cost(vertices,vertex_areas));
33   COST(1e3,    noncircular_rim_cost(vertices));
34   
35   printf("| total %# e |", energy);
36   if (energy < best_energy) {
37     FILE *best;
38     printf(" BEST");
39     
40     best_f= fopen(BEST_F ".new","wb");  if (!best_f) diee("fopen new best");
41     r= fwrite(vertices,sizeof(vertices),1,best_f); if (r!=1) diee("fwrite");
42     if (fclose(best_f)) diee("fclose new best");
43     if (rename(BEST_F ".new", BEST_F)) diee("rename install new best");
44   }
45   putchar('\n');
46   flushoutput();
47
48   return energy;
49 }    
50
51 static void cost(double *energy, double tweight, double tcost) {
52   double tenergy= tweight * tcost;
53   printf(" %# e > %# e |", tcost, tenergy);
54   *energy += tenergy;
55 }
56
57 static void flushoutput(void) {
58   if (fflush(stdout) || ferror(stdout)) { perror("stdout"); exit(-1); }
59 }
60
61 static void compute_vertex_areas(const Vertices vertices, double areas[N]) {
62   FOR_VERTEX(v0) {
63     double total= 0.0;
64     int count= 0;
65     
66     FOR_VEDGE(v0,e1,v1) {
67       e2= (e1+1) % V6;
68       v2= EDGE_END2(v0,e2);
69       if (v2<0) continue;
70       
71       double e1v[D3], e2v[D3], av[D3];
72       K {
73         e1v[k]= vertices[v1][k] - vertices[v0][k];
74         e2v[k]= vertices[v2][k] - vertices[v0][k];
75       }
76       xprod(av, e1v, e2v);
77       total += hypotD1(av);
78       count++;
79     }
80     areas[v0]= total / count;
81   }
82 }
83
84 /*---------- use of GSL ----------*/
85
86   /* We want to do multidimensional minimisation.
87    *
88    * We don't think there are any local minima.  Or at least, if there
89    * are, the local minimum which will be found from the starting
90    * state is the one we want.
91    *
92    * We don't want to try to provide a derivative of the cost
93    * function.  That's too tedious (and anyway the polynomial
94    * approximation to our our cost function sometimes has high degree
95    * in the inputs which means the quadratic model implied by most of
96    * the gradient descent minimisers is not ideal).
97    *
98    * This eliminates most of the algorithms.  Nelder and Mead's
99    * simplex algorithm is still available and we will try that.
100    *
101    * In our application we are searching for the optimal locations of
102    * N actualvertices in D3 (3) dimensions - ie, we are searching for
103    * the optimal metapoint in an N*D3-dimensional space.
104    * 
105    * So eg with X=Y=100, the simplex will contain 300 metavertices
106    * each of which is an array of 300 doubles for the actualvertex
107    * coordinates.  Hopefully this won't be too slow ...
108    */
109
110 static void gsldie(const char *what, int status) {
111   fprintf(stderr,"gsl function failed: %s: %s\n", what, gsl_strerror(status));
112   exit(-1);
113 }
114
115 static gsl_multimin_fminimizer *minimiser;
116
117 static const stop_epsilon= 1e-4;
118
119 #define DIM (N*D3)
120
121 static double minfunc_f(const gsl_vector *x, void *params) {
122   assert(x->size == DIM);
123   assert(x->stride == 1);
124   return compute_energy((Vertices)x->data);
125 }
126
127 int main(int argc, const char *const *argv) {
128   struct gsl_multimin_function multimin_function;
129   double size;
130   Vertices initial, step_size;
131   FILE *initial;
132   gsl_vector initial_gsl, step_size_gsl;
133   int r;
134   
135   if (argc>1) { fputs("takes no arguments\n",stderr); exit(8); }
136
137   minimiser= gsl_multimin_fminimizer_alloc
138     (gsl_multimin_fminimizer_nmsimplex, DIM);
139   if (!minimiser) { perror("alloc minimiser"); exit(-1); }
140
141   multimin_function.f= minfunc_f;
142   multimin_function.n= DIM;
143   multimin_function.params= 0;
144
145   initial_f= fopen(INITIAL_F,"rb");  if (!initial_f) diee("fopen initial");
146   errno= 0; r= fread(initial,sizeof(initial),1,initial_f);
147   if (r!=1) diee("fread");
148   fclose(initial_f);
149
150   initial_gsl.size= DIM;
151   initial_gsl.stride= 1;
152   initial_gsl.block= 0;
153   initial_gsl.owner= 0;
154   step_size_gsl= initial_gsl;
155
156   initial_gsl.data= initial;
157   step_size_gsl.data= step_size;
158
159   FOR_VERTEX(v)
160     K step_size[v][k]= 1e-3;
161   FOR_RIM_VERTEX(vx,vy,v)
162     step_size[v][3] *= 0.1;
163   
164   for (vy=0; vy<Y; vy+=Y-1)
165   for (vx=0; vx<x
166   for (i=0; i<DIM; i++) step_size[i]= step_size;
167   
168
169   step_size= gsl_vector_alloc(DIM);  if (!step_size) gsldie("alloc step");
170   gsl_vector_set_all(step_size, 1e-3);
171
172   assert(step_
173   step_
174
175   r= gsl_multimin_fminimizer_set(minimiser, &multimin_function,
176                                  &initial_gsl, &step_size);
177   if (r) { gsldie("fminimizer_set",r); }
178   
179   for (;;) {
180     r= gsl_multimin_fminimizer_iterate(minimiser);
181     if (r) { gsldie("fminimizer_iterate",r); }
182
183     size= gsl_multimin_fminimizer_size(minimiser);
184     r= gsl_multimin_test_size(size, stop_epsilon);
185
186     printf("size %# e, r=%d\n", size, r);
187     flushoutput();
188
189     if (r==GSL_SUCCESS) break;
190     assert(r==GSL_CONTINUE);
191   }
192 }
193
194 /*---------- Edgewise vertex displacement ----------*/
195
196   /*
197    *  
198    *
199    *
200    *                Q `-_
201    *              / |    `-_
202    *   R' - _ _ _/_ |       `-.
203    *    .       /   M - - - - - S
204    *    .      /    |      _,-'
205    *    .     /     |  _,-'
206    *    .    /    , P '
207    *    .   /  ,-'
208    *    .  /,-'
209    *    . /'
210    *     R
211    *
212    *
213    *
214    *  Find R', the `expected' location of R, by
215    *  reflecting S in M (the midpoint of QP).
216    *
217    *  Let 2d = |RR'|
218    *       b = |PQ|
219    *       l = |RS|
220    *
221    *  Giving energy contribution:
222    *
223    *                               2
224    *                            b d
225    *    E             =  F   .  ----
226    *     vd, edge PQ      vd      3
227    *                             l
228    *
229    *  (The dimensions of this are those of F_vd.)
230    *
231    *  By symmetry, this calculation gives the same answer with R and S
232    *  exchanged.  Looking at the projection in the RMS plane:
233    *
234    *
235    *                           S'
236    *                         ,'
237    *                       ,'
238    *    R'               ,'             2d" = |SS'| = |RR'| = 2d
239    *      `-._         ,'
240    *          `-._   ,'                 By congruent triangles,
241    *              ` M                   with M' = midpoint of RS,
242    *              ,'  `-._              |MM'| = |RR'|/2 = d
243    *            ,'        `-._
244    *          ,'              ` S       So use
245    *        ,'       M' _ , - '            d = |MM'|
246    *      ,'   _ , - '
247    *     R - '
248    *
249    *  We choose this value for l (rather than |RM|+|MS|, say, or |RM|)
250    *  because we want this symmetry and because we're happy to punish
251    *  bending more than uneveness in the metric.
252    *
253    *  In practice to avoid division by zero we'll add epsilon to l^3
254    *  and the huge energy ought then to be sufficient for the model to
255    *  avoid being close to R=S.
256    */
257
258 static double edgewise_vertex_displacement_cost(const Vertices vertices) {
259   static const l3_epsison= 1e-6;
260
261   int pi,e,qi,ri,si, k;
262   double m[D3], mprime[D3], b, d2, l, sigma_bd2_l3;
263
264   FOR_EDGE(pi,e,qi) {
265     ri= EDGE_END2(pi,(e+1)%V6); if (r<0) continue;
266     si= EDGE_END2(pi,(e+5)%V6); if (s<0) continue;
267     assert(ri == EDGE_END2(qi,(e+2)%V6));
268     assert(si == EDGE_END2(qi,(e+4)%V6));
269     
270     K m[k]= (vertices[pi][k] + vertices[qi][k]) * 0.5;
271     K mprime[k]= (vertices[ri][k] + vertices[si][k]) * 0.5;
272     b= hypotD(vertices[pi], vertices[qi]);
273     d2= hypotD2(m, mprime);
274     l= hypotD(vertices[ri][k] - vertices[si][k]);
275     l3 = l*l*l + l3_epsilon;
276
277     sigma_bd2_l3 += b * d2 / l3;
278   }
279   return sigma_bd2_l3;
280 }
281
282 /*---------- noncircular rim cost ----------*/
283
284 static double noncircular_rim_cost(Vertices vertices) {
285   int vy,vx,v;
286   double cost= 0.0;
287   
288   FOR_RIM_VERTEX(vy,vx,v) {
289     double oncircle[3];
290     /* By symmetry, nearest point on circle is the one with
291      * the same angle subtended at the z axis. */
292     oncircle[0]= vertices[v][0];
293     oncircle[1]= vertices[v][1];
294     oncircle[2]= 0;
295     double mult= 1.0/ magnD(oncircle);
296     oncircle[0] *= mult;
297     oncircle[1] *= mult;
298     double d2= hypotD2(vertices[v], oncircle);
299     cost += d2*d2;
300   }
301 }