chiark / gitweb /
Check max Hamming weight in the other direction.
[matchsticks-search.git] / main.c
1 /*
2  * Searches for "good" ways to divide n matchsticks up and reassemble them
3  * into m matchsticks.  "Good" means the smallest fragment is as big
4  * as possible.
5  *
6  * Invoke as   ./main n m
7  *
8  * The algorithm is faster if the arguments are ordered so that n > m.
9  */
10
11 /*
12  * matchsticks/main.c  Copyright 2014 Ian Jackson
13  *
14  * This program is free software: you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation, either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  */
24
25 #include <stdio.h>
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <assert.h>
30 #include <stdbool.h>
31 #include <inttypes.h>
32
33 #include <publib.h>
34 #include <glpk.h>
35
36 /*
37  * Algorithm.
38  *
39  * Each input match contributes, or does not contribute, to each
40  * output match; we do not need to consider multiple fragments
41  * relating to the same input/output pair this gives an n*m adjacency
42  * matrix (bitmap).  Given such an adjacency matrix, the problem of
43  * finding the best sizes for the fragments can be expressed as a
44  * linear programming problem.
45  *
46  * We search all possible adjacency matrices, and for each one we run
47  * GLPK's simplex solver.  We represent the adjacency matrix as an
48  * array of bitmaps.
49  *
50  * However, there are a couple of wrinkles:
51  *
52  * To best represent the problem as a standard LP problem, we separate
53  * out the size of each fragment into a common minimum size variable,
54  * plus a fragment-specific extra size variable.  This reduces the LP
55  * problem size at the cost of making the problem construction, and
56  * interpretation of the results, a bit fiddly.
57  *
58  * Many of the adjacency matrices are equivalent.  In particular,
59  * permutations of the columns, or of the rows, do not change the
60  * meaning.  It is only necessasry to consider any one permutation.
61  * We make use of this by considering only adjacency matrices whose
62  * bitmap array contains bitmap words whose numerical values are
63  * nondecreasing in array order.
64  *
65  * Once we have a solution, we also avoid considering any candidate
66  * which involves dividing one of the output sticks into so many
67  * fragment that the smallest fragment would necessarily be no bigger
68  * than our best solution.  That is, we reject candidates where any of
69  * the hamming weights of the adjacency bitmap words are too large.
70  *
71  * And, we want to do the search in order of increasing maximum
72  * hamming weight.  This is because in practice optimal solutions tend
73  * to have low hamming weight, and having found a reasonable solution
74  * early allows us to eliminate a lot of candidates without doing the
75  * full LP.
76  */
77
78 typedef uint32_t AdjWord;
79 #define PRADJ "08"PRIx32
80
81 static int n, m, maxhamweight;
82 static AdjWord *adjmatrix;
83 static AdjWord adjall;
84
85 static double best;
86 static glp_prob *best_prob;
87 static AdjWord *best_adjmatrix;
88
89 static int n_over_best;
90 static int *weight;
91
92 static unsigned printcounter;
93
94 static AdjWord *xalloc_adjmatrix(void) {
95   return xmalloc(sizeof(*adjmatrix)*n);
96 }
97
98 static void prep(void) {
99   adjall = ~((~(AdjWord)0) << m);
100   adjmatrix = xalloc_adjmatrix();
101   glp_term_out(GLP_OFF);
102   n_over_best = n / best;
103   weight = (int *)malloc(m*sizeof(int));
104   for (int j = 0; j < m; j++) weight[j] = 0;
105 }
106
107 static AdjWord one_adj_bit(int bitnum) {
108   return (AdjWord)1 << bitnum;
109 }
110
111 static int count_set_adj_bits(AdjWord w) {
112   int j, total;
113   for (j=0, total=0; j<m; j++)
114     total += !!(w & one_adj_bit(j));
115   return total;
116 }
117
118 static void optimise(int doprint) {
119   /* Consider the best answer (if any) for a given adjacency matrix */
120   glp_prob *prob = 0;
121   int i, j, totalfrags;
122
123   /*
124    * Up to a certain point, optimise() can be restarted.  We use this
125    * to go back and print the debugging output if it turns out that we
126    * have an interesting case.  The HAVE_PRINTED macro does this: its
127    * semantics are to go back in time and make sure that we have
128    * printed the description of the search case.
129    */
130 #define HAVE_PRINTED ({                                         \
131       if (!doprint) { doprint = 1; goto retry_with_print; }     \
132     })
133  retry_with_print:
134   if (prob) {
135     glp_delete_prob(prob);
136     prob = 0;
137   }
138
139 #define PRINTF(...) if (!doprint) ; else fprintf(stderr, __VA_ARGS__) /* bodgy */
140
141   PRINTF("%2d ", maxhamweight);
142
143   bool had_max = 0;
144   for (i=0, totalfrags=0; i<n; i++) {
145     int frags = count_set_adj_bits(adjmatrix[i]);
146     had_max += (frags == maxhamweight);
147     totalfrags += frags;
148     PRINTF("%"PRADJ" ", adjmatrix[i]);
149     double maxminsize = (double)m / frags;
150     if (maxminsize <= best) {
151       PRINTF(" too fine");
152       goto out;
153     }
154   }
155   if (!had_max) {
156     /* Skip this candidate as its max hamming weight is lower than
157      * we're currently looking for (which means we must have done it
158      * already).  (The recursive iteration ensures that none of the
159      * words have more than the max hamming weight.) */
160     PRINTF(" nomaxham");
161     goto out;
162   }
163
164   /*
165    * We formulate our problem as an LP problem as follows.
166    * In this file "n" and "m" are the matchstick numbers.
167    *
168    * Each set bit in the adjacency matrix corresponds to taking a
169    * fragment from old match i and making it part of new match j.
170    *
171    * The structural variables (columns) are:
172    *   x_minimum        minimum size of any fragment (bounded below by 0)
173    *   x_morefrag_i_j   the amount by which the size of the fragment
174    *                     i,j exceeds the minimum size (bounded below by 0)
175    *
176    * The auxiliary variables (rows) are:
177    *   x_total_i       total length for each input match (fixed variable)
178    *   x_total_j       total length for each output match (fixed variable)
179    *
180    * The objective function is simply
181    *   maximise x_minimum
182    *
183    * We use X_ and Y_ to refer to GLPK's (1-based) column and row indices.
184    * ME_ refers to entries in the list of constraint matrix elements
185    * which we build up as we go.
186    */
187
188   prob = glp_create_prob();
189
190   int Y_totals_i = glp_add_rows(prob, n);
191   int Y_totals_j = glp_add_rows(prob, m);
192   int X_minimum = glp_add_cols(prob, 1);
193
194   {
195   int next_matrix_entry = 1; /* wtf GLPK! */
196   int matrix_entries_size = next_matrix_entry + n + m + totalfrags*2;
197   double matrix_entries[matrix_entries_size];
198   int matrix_entries_XY[2][matrix_entries_size];
199
200 #define ADD_MATRIX_ENTRY(Y,X) ({                        \
201       assert(next_matrix_entry < matrix_entries_size);  \
202       matrix_entries_XY[0][next_matrix_entry] = (X);    \
203       matrix_entries_XY[1][next_matrix_entry] = (Y);    \
204       matrix_entries[next_matrix_entry] = 0;            \
205       next_matrix_entry++;                              \
206     })
207
208   int ME_totals_i__minimum = next_matrix_entry;
209   for (i=0; i<n; i++) ADD_MATRIX_ENTRY(Y_totals_i+i, X_minimum);
210
211   int ME_totals_j__minimum = next_matrix_entry;
212   for (j=0; j<m; j++) ADD_MATRIX_ENTRY(Y_totals_j+j, X_minimum);
213
214   /* \forall_i x_total_i = m */
215   /* \forall_i x_total_j = n */
216   for (i=0; i<n; i++) glp_set_row_bnds(prob, Y_totals_i+i, GLP_FX, m,m);
217   for (j=0; j<m; j++) glp_set_row_bnds(prob, Y_totals_j+j, GLP_FX, n,n);
218
219   /* x_minimum >= 0 */
220   glp_set_col_bnds(prob, X_minimum, GLP_LO, 0, 0);
221   glp_set_col_name(prob, X_minimum, "minimum");
222
223   /* objective is maximising x_minimum */
224   glp_set_obj_dir(prob, GLP_MAX);
225   glp_set_obj_coef(prob, X_minimum, 1);
226
227   for (i=0; i<n; i++) {
228     for (j=0; j<m; j++) {
229       if (!(adjmatrix[i] & one_adj_bit(j)))
230         continue;
231       /* x_total_i += x_minimum */
232       /* x_total_j += x_minimum */
233       matrix_entries[ ME_totals_i__minimum + i ] ++;
234       matrix_entries[ ME_totals_j__minimum + j ] ++;
235
236       /* x_morefrag_i_j >= 0 */
237       int X_morefrag_i_j = glp_add_cols(prob, 1);
238       glp_set_col_bnds(prob, X_morefrag_i_j, GLP_LO, 0, 0);
239       if (doprint) {
240         char buf[255];
241         snprintf(buf,sizeof(buf),"mf %d,%d",i,j);
242         glp_set_col_name(prob, X_morefrag_i_j, buf);
243       }
244
245       /* x_total_i += x_morefrag_i_j */
246       /* x_total_j += x_morefrag_i_j */
247       int ME_totals_i__mf_i_j = ADD_MATRIX_ENTRY(Y_totals_i+i, X_morefrag_i_j);
248       int ME_totals_j__mf_i_j = ADD_MATRIX_ENTRY(Y_totals_j+j, X_morefrag_i_j);
249       matrix_entries[ME_totals_i__mf_i_j] = 1;
250       matrix_entries[ME_totals_j__mf_i_j] = 1;
251     }
252   }
253
254   assert(next_matrix_entry == matrix_entries_size);
255
256   glp_load_matrix(prob, matrix_entries_size-1,
257                   matrix_entries_XY[1], matrix_entries_XY[0],
258                   matrix_entries);
259
260   int r = glp_simplex(prob, NULL);
261   PRINTF(" glp=%d", r);
262
263 #define OKERR(e) \
264   case e: PRINTF(" " #e ); goto out;
265 #define BADERR(e) \
266   case e: HAVE_PRINTED; printf(" " #e " CRASHING\n"); exit(-1);
267 #define DEFAULT \
268   default: HAVE_PRINTED; printf(" ! CRASHING\n"); exit(-1);
269
270   switch (r) {
271   OKERR(GLP_ESING);
272   OKERR(GLP_ECOND);
273   OKERR(GLP_EBOUND);
274   OKERR(GLP_EFAIL);
275   OKERR(GLP_ENOPFS);
276   OKERR(GLP_ENODFS);
277   BADERR(GLP_EBADB);
278   BADERR(GLP_EOBJLL);
279   BADERR(GLP_EOBJUL);
280   BADERR(GLP_EITLIM);
281   BADERR(GLP_ETMLIM);
282   BADERR(GLP_EINSTAB);
283   BADERR(GLP_ENOCVG);
284   case 0: break;
285   DEFAULT;
286   }
287
288   r = glp_get_status(prob);
289   PRINTF(" status=%d", r);
290
291   switch (r) {
292   OKERR(GLP_NOFEAS);
293   OKERR(GLP_UNDEF);
294   BADERR(GLP_FEAS);
295   BADERR(GLP_INFEAS);
296   BADERR(GLP_UNBND);
297   case GLP_OPT: break;
298   DEFAULT;
299   }
300
301   double got = glp_get_obj_val(prob);
302   PRINTF("  %g", got);
303   if (got <= best)
304     goto out;
305
306   HAVE_PRINTED;
307
308   best = got;
309   n_over_best = n / best;
310
311   if (best_prob) glp_delete_prob(best_prob);
312   best_prob = prob;
313
314   free(best_adjmatrix);
315   best_adjmatrix = xalloc_adjmatrix();
316   memcpy(best_adjmatrix, adjmatrix, sizeof(*adjmatrix)*n);
317
318   PRINTF(" BEST        \n");
319   return;
320
321   }
322  out:
323   if (prob)
324     glp_delete_prob(prob);
325   if (doprint) { PRINTF("        \r"); fflush(stdout); }
326 }
327
328 static void iterate_recurse(int i, AdjWord min) {
329   if (i >= n) {
330     printcounter++;
331     optimise(!(printcounter & 0xfff));
332     return;
333   }
334   for (adjmatrix[i] = min;
335        ;
336        adjmatrix[i]++) {
337     if (count_set_adj_bits(adjmatrix[i]) > maxhamweight)
338       goto again;
339     if (i == 0 && (adjmatrix[i] & (1+adjmatrix[i])))
340       goto again;
341     for (int j = 0; j < m; j++)
342       if (adjmatrix[i] & (1 << j))
343         weight[j]++;
344     for (int j = 0; j < m; j++)
345       if (weight[j] >= n_over_best)
346         goto takeout;
347
348     iterate_recurse(i+1, adjmatrix[i]);
349
350    takeout:
351     for (int j = 0; j < m; j++)
352       if (adjmatrix[i] & (1 << j))
353         weight[j]--;
354
355   again:
356     if (adjmatrix[i] == adjall)
357       return;
358   }
359 }
360
361 static void iterate(void) {
362   for (maxhamweight=1; maxhamweight<=m; maxhamweight++) {
363     double maxminsize = (double)m / maxhamweight;
364     if (maxminsize <= best)
365       continue;
366
367     iterate_recurse(0, 1);
368   }
369 }
370
371 int main(int argc, char **argv) {
372   assert(argc==3);
373   n = atoi(argv[1]);
374   m = atoi(argv[2]);
375   prep();
376   iterate();
377   fprintf(stderr, "\n");
378   if (best_prob) {
379     double min = glp_get_obj_val(best_prob);
380     double a[n][m];
381     int i, j, cols;
382     for (i = 0; i < n; i++)
383       for (j = 0; j < m; j++)
384         a[i][j] = 0;
385     cols = glp_get_num_cols(best_prob);
386     for (i = 1; i <= cols; i++) {
387       int x, y;
388       if (2 != sscanf(glp_get_col_name(best_prob, i), "mf %d,%d", &x, &y))
389         continue;
390       a[x][y] = min + glp_get_col_prim(best_prob, i);
391     }
392     printf("%d into %d: min fragment %g\n", n, m, min);
393     for (i = 0; i < n; i++) {
394       for (j = 0; j < m; j++) {
395         if (a[i][j])
396           printf(" %9.3f", a[i][j]);
397         else
398           printf("          ");
399       }
400       printf("\n");
401     }
402   }
403   if (ferror(stdout) || fclose(stdout)) { perror("stdout"); exit(-1); }
404   return 0;
405 }