chiark / gitweb /
assert our arguments
[matchsticks-search.git] / main.c
1
2 #include <stdio.h>
3 #include <stdint.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <assert.h>
7 #include <stdbool.h>
8 #include <inttypes.h>
9
10 #include <publib.h>
11 #include <glpk.h>
12
13 typedef uint32_t AdjWord;
14 #define PRADJ "08"PRIx32
15
16 static int n, m, maxhamweight;
17 static AdjWord *adjmatrix;
18 static AdjWord adjall;
19
20 static double best;
21 static glp_prob *best_prob;
22 static AdjWord *best_adjmatrix;
23
24 static unsigned printcounter;
25
26 static AdjWord *xalloc_adjmatrix(void) {
27   return xmalloc(sizeof(*adjmatrix)*n);
28 }
29
30 static void prep(void) {
31   adjall = ~((~(AdjWord)0) << m);
32   adjmatrix = xalloc_adjmatrix();
33   glp_term_out(GLP_OFF);
34 }
35
36 static AdjWord one_adj_bit(int bitnum) {
37   return (AdjWord)1 << bitnum;
38 }
39
40 static int count_set_adj_bits(AdjWord w) {
41   int j, total;
42   for (j=0, total=0; j<m; j++)
43     total += !!(w & one_adj_bit(j));
44   return total;
45 }
46
47 static void optimise(int doprint) {
48   glp_prob *prob = 0;
49   int i, j, totalfrags;
50
51 #define HAVE_PRINTED ({                                         \
52       if (!doprint) { doprint = 1; goto retry_with_print; }     \
53     })
54  retry_with_print:
55   if (prob) {
56     glp_delete_prob(prob);
57     prob = 0;
58   }
59
60 #define PRINTF if (!doprint) ; else printf /* bodgy */
61
62   PRINTF("%2d ", maxhamweight);
63
64   bool had_max = 0;
65   for (i=0, totalfrags=0; i<n; i++) {
66     int frags = count_set_adj_bits(adjmatrix[i]);
67     had_max += (frags == maxhamweight);
68     totalfrags += frags;
69     PRINTF("%"PRADJ" ", adjmatrix[i]);
70     double maxminsize = (double)m / frags;
71     if (maxminsize <= best) {
72       PRINTF(" too fine");
73       goto out;
74     }
75   }
76   if (!had_max) {
77     PRINTF(" nomaxham");
78     goto out;
79   }
80
81   /*
82    * We formulate our problem as an LP problem as follows.
83    * In this file "n" and "m" are the matchstick numbers.
84    *
85    * Each set bit in the adjacency matrix corresponds to taking a
86    * fragment from old match i and making it part of new match j.
87    *
88    * The structural variables (columns) are:
89    *   x_minimum        minimum size of any fragment (bounded below by 0)
90    *   x_morefrag_i_j   the amount by which the size of the fragment
91    *                     i,j exceeds the minimum size (bounded below by 0)
92    *
93    * The auxiliary variables (rows) are:
94    *   x_total_i       total length for each input match (fixed variable)
95    *   x_total_j       total length for each output match (fixed variable)
96    *
97    * The objective function is simply
98    *   maximise x_minimum
99    *
100    * We use X_ and Y_ to refer to GLPK's (1-based) column and row indices.
101    * ME_ refers to entries in the list of constraint matrix elements
102    * which we build up as we go.
103    */
104
105   prob = glp_create_prob();
106
107   int Y_totals_i = glp_add_rows(prob, n);
108   int Y_totals_j = glp_add_rows(prob, m);
109   int X_minimum = glp_add_cols(prob, 1);
110
111   {
112   int next_matrix_entry = 1; /* wtf GLPK! */
113   int matrix_entries_size = next_matrix_entry + n + m + totalfrags*2;
114   double matrix_entries[matrix_entries_size];
115   int matrix_entries_XY[2][matrix_entries_size];
116
117 #define ADD_MATRIX_ENTRY(Y,X) ({                        \
118       assert(next_matrix_entry < matrix_entries_size);  \
119       matrix_entries_XY[0][next_matrix_entry] = (X);    \
120       matrix_entries_XY[1][next_matrix_entry] = (Y);    \
121       matrix_entries[next_matrix_entry] = 0;            \
122       next_matrix_entry++;                              \
123     })
124
125   int ME_totals_i__minimum = next_matrix_entry;
126   for (i=0; i<n; i++) ADD_MATRIX_ENTRY(Y_totals_i+i, X_minimum);
127
128   int ME_totals_j__minimum = next_matrix_entry;
129   for (j=0; j<m; j++) ADD_MATRIX_ENTRY(Y_totals_j+j, X_minimum);
130
131   /* \forall_i x_totals_i = m */
132   /* \forall_i x_totals_j = n */
133   for (i=0; i<n; i++) glp_set_row_bnds(prob, Y_totals_i+i, GLP_FX, m,m);
134   for (j=0; j<m; j++) glp_set_row_bnds(prob, Y_totals_j+j, GLP_FX, n,n);
135
136   /* x_minimum >= 0 */
137   glp_set_col_bnds(prob, X_minimum, GLP_LO, 0, 0);
138   glp_set_col_name(prob, X_minimum, "minimum");
139
140   /* objective is maximising x_minimum */
141   glp_set_obj_dir(prob, GLP_MAX);
142   glp_set_obj_coef(prob, X_minimum, 1);
143
144   for (i=0; i<n; i++) {
145     for (j=0; j<m; j++) {
146       if (!(adjmatrix[i] & one_adj_bit(j)))
147         continue;
148       /* x_total_i += x_minimum */
149       /* x_total_j += x_minimum */
150       matrix_entries[ ME_totals_i__minimum + i ] ++;
151       matrix_entries[ ME_totals_j__minimum + j ] ++;
152
153       /* x_morefrag_i_j >= 0 */
154       int X_morefrag_i_j = glp_add_cols(prob, 1);
155       glp_set_col_bnds(prob, X_morefrag_i_j, GLP_LO, 0, 0);
156       if (doprint) {
157         char buf[255];
158         snprintf(buf,sizeof(buf),"mf %d,%d",i,j);
159         glp_set_col_name(prob, X_morefrag_i_j, buf);
160       }
161
162       /* x_total_i += x_morefrag_i_j */
163       /* x_total_j += x_morefrag_i_j */
164       int ME_totals_i__mf_i_j = ADD_MATRIX_ENTRY(Y_totals_i+i, X_morefrag_i_j);
165       int ME_totals_j__mf_i_j = ADD_MATRIX_ENTRY(Y_totals_j+j, X_morefrag_i_j);
166       matrix_entries[ME_totals_i__mf_i_j] = 1;
167       matrix_entries[ME_totals_j__mf_i_j] = 1;
168     }
169   }
170
171   assert(next_matrix_entry == matrix_entries_size);
172
173   glp_load_matrix(prob, matrix_entries_size-1,
174                   matrix_entries_XY[1], matrix_entries_XY[0],
175                   matrix_entries);
176
177   int r = glp_simplex(prob, NULL);
178   PRINTF(" glp=%d", r);
179
180 #define OKERR(e) \
181   case e: PRINTF(" " #e ); goto out;
182 #define BADERR(e) \
183   case e: HAVE_PRINTED; printf(" " #e " CRASHING\n"); exit(-1);
184 #define DEFAULT \
185   default: HAVE_PRINTED; printf(" ! CRASHING\n"); exit(-1);
186
187   switch (r) {
188   OKERR(GLP_ESING);
189   OKERR(GLP_ECOND);
190   OKERR(GLP_EBOUND);
191   OKERR(GLP_EFAIL);
192   OKERR(GLP_ENOPFS);
193   OKERR(GLP_ENODFS);
194   BADERR(GLP_EBADB);
195   BADERR(GLP_EOBJLL);
196   BADERR(GLP_EOBJUL);
197   BADERR(GLP_EITLIM);
198   BADERR(GLP_ETMLIM);
199   BADERR(GLP_EINSTAB);
200   BADERR(GLP_ENOCVG);
201   case 0: break;
202   DEFAULT;
203   }
204
205   r = glp_get_status(prob);
206   PRINTF(" status=%d", r);
207
208   switch (r) {
209   OKERR(GLP_NOFEAS);
210   OKERR(GLP_UNDEF);
211   BADERR(GLP_FEAS);
212   BADERR(GLP_INFEAS);
213   BADERR(GLP_UNBND);
214   case GLP_OPT: break;
215   DEFAULT;
216   }
217
218   double got = glp_get_obj_val(prob);
219   PRINTF("  %g", got);
220   if (got <= best)
221     goto out;
222
223   HAVE_PRINTED;
224
225   best = got;
226
227   if (best_prob) glp_delete_prob(best_prob);
228   best_prob = prob;
229
230   free(best_adjmatrix);
231   best_adjmatrix = xalloc_adjmatrix();
232   memcpy(best_adjmatrix, adjmatrix, sizeof(*adjmatrix)*n);
233
234   printf(" BEST        \n");
235   return;
236
237   }
238  out:
239   if (prob)
240     glp_delete_prob(prob);
241   if (doprint) { printf("        \r"); fflush(stdout); }
242 }
243
244 static void iterate_recurse(int i, AdjWord min) {
245   if (i >= n) {
246     printcounter++;
247     optimise(!(printcounter & 0xfff));
248     return;
249   }
250   for (adjmatrix[i] = min;
251        ;
252        adjmatrix[i]++) {
253     if (count_set_adj_bits(adjmatrix[i]) > maxhamweight)
254       goto again;
255
256     iterate_recurse(i+1, adjmatrix[i]);
257
258   again:
259     if (adjmatrix[i] == adjall)
260       return;
261   }
262 }
263
264 static void iterate(void) {
265   for (maxhamweight=1; maxhamweight<=m; maxhamweight++) {
266     double maxminsize = (double)m / maxhamweight;
267     if (maxminsize <= best)
268       continue;
269
270     iterate_recurse(0, 1);
271   }
272 }
273
274 int main(int argc, char **argv) {
275   assert(argc==3);
276   n = atoi(argv[1]);
277   m = atoi(argv[2]);
278   prep();
279   iterate();
280   printf("\n");
281   if (best_prob)
282     glp_print_sol(best_prob,"/dev/stdout");
283   if (ferror(stdout) || fclose(stdout)) { perror("stdout"); exit(-1); }
284   return 0;
285 }