Restore the General Pattern Matcher
[pachi.git] / patternscan / patternscan.c
blob1479858c1fce47e132d40f22c0b16fb6436ccfae
1 #include <assert.h>
2 #include <stdio.h>
3 #include <stdlib.h>
5 #include "board.h"
6 #include "debug.h"
7 #include "engine.h"
8 #include "move.h"
9 #include "patternscan/patternscan.h"
10 #include "pattern.h"
11 #include "patternsp.h"
14 /* Internal engine state. */
15 struct patternscan {
16 int debug_level;
18 struct pattern_config pc;
19 pattern_spec ps;
20 bool competition;
21 bool mm;
23 bool no_pattern_match;
24 bool gen_spat_dict;
25 /* Minimal number of occurences for spatial to be saved;
26 * 3x3 spatials are always saved. */
27 int spat_threshold;
28 /* Number of loaded spatials; checkpoint for saving new sids
29 * in case gen_spat_dict is enabled. */
30 int loaded_spatials;
32 /* Book-keeping of spatial occurence count. */
33 int nscounts;
34 int *scounts;
38 /* Starting gamma number of each feature. */
39 static int gammaid[FEAT_MAX + MAX_PATTERN_DIST + 1];
40 /* For each spatial id, its gamma value. */
41 static int spatg[65536];
43 /* Print MM-format header - summary of features. Also create patterns.fdict
44 * containing mapping from gamma numbers to feature:payload pairs. */
45 static void
46 mm_header(struct patternscan *ps)
48 FILE *fdict = fopen("patterns.fdict", "w");
49 if (!fdict) {
50 perror("patterns.fdict");
51 exit(EXIT_FAILURE);
54 gammaid[0] = 0;
55 int g = 0;
56 int features = 0;
57 for (int i = 0; i < FEAT_MAX; i++) {
58 if (ps->ps[i] == 0) {
59 /* Feature disabled. */
60 /* XXX: Accurately account for payload count
61 * of partially disabled features? */
62 gammaid[i + 1] = gammaid[i];
63 continue;
65 if (i == FEAT_SPATIAL) {
66 /* Special handling. */
67 gammaid[i + 1] = gammaid[i];
68 continue;
71 features++;
73 gammaid[i + 1] = gammaid[i] + feature_payloads(&ps->pc, i);
75 for (int p = 0; p < feature_payloads(&ps->pc, i); p++) {
76 struct feature f = { .id = i, .payload = p };
77 char buf[256] = ""; feature2str(buf, &f);
78 fprintf(fdict, "%d %s\n", g++, buf);
80 assert(g == gammaid[i + 1]);
83 /* We need to break down spatials by their radius, since payloads
84 * of single feature must be independent. */
85 if (ps->ps[FEAT_SPATIAL] != 0) {
86 for (int d = 0; d <= ps->pc.spat_max - ps->pc.spat_min; d++) {
87 for (int i = 0; i < ps->pc.spat_dict->nspatials; i++) {
88 if (ps->pc.spat_dict->spatials[i].dist != ps->pc.spat_min + d)
89 continue;
90 spatg[i] = g++;
91 struct feature f = { .id = FEAT_SPATIAL, .payload = i };
92 char buf[256] = ""; feature2str(buf, &f);
93 fprintf(fdict, "%d %s\n", spatg[i], buf);
95 features++;
96 gammaid[FEAT_MAX + d + 1] = g;
100 fclose(fdict);
102 printf("! %d\n", g); // Number of gammas.
103 printf("%d\n", features); // Number of features.
104 for (int i = 0; i < FEAT_MAX; i++) {
105 if (ps->ps[i] == 0) continue;
106 if (i == FEAT_SPATIAL) continue;
107 // Number of gammas per feature.
108 printf("%d %s\n", gammaid[i + 1] - gammaid[i], feature_name(i));
110 if (ps->ps[FEAT_SPATIAL] != 0) {
111 for (int d = 0; d <= ps->pc.spat_max - ps->pc.spat_min; d++) {
112 printf("%d %s.%d\n", gammaid[FEAT_MAX + d + 1] - gammaid[FEAT_MAX + d],
113 feature_name(FEAT_SPATIAL), ps->pc.spat_min + d);
116 printf("!\n");
119 static char *
120 mm_pattern(struct patternscan *ps, char *str, struct pattern *p)
122 for (int i = 0; i < p->n; i++) {
123 if (i > 0) str = stpcpy(str, " ");
124 switch (p->f[i].id) {
125 case FEAT_SPATIAL:
126 str += sprintf(str, "%d", spatg[p->f[i].payload]);
127 break;
128 default:
129 str += sprintf(str, "%d", gammaid[p->f[i].id] + p->f[i].payload);
130 break;
133 return stpcpy(str, "\n");
137 static void
138 process_pattern(struct patternscan *ps, struct board *b, struct move *m, char **str)
140 /* First, store the spatial configuration in dictionary
141 * if applicable. */
142 if (ps->gen_spat_dict && !is_pass(m->coord)) {
143 struct spatial s;
144 spatial_from_board(&ps->pc, &s, b, m);
145 int dmax = s.dist;
146 for (int d = ps->pc.spat_min; d <= dmax; d++) {
147 s.dist = d;
148 int sid = spatial_dict_put(ps->pc.spat_dict, &s, spatial_hash(0, &s));
149 assert(sid > 0);
150 /* Allocate space in 1024 blocks. */
151 #define SCOUNTS_ALLOC 1024
152 if (sid >= ps->nscounts) {
153 int newnsc = (sid / SCOUNTS_ALLOC + 1) * SCOUNTS_ALLOC;
154 ps->scounts = realloc(ps->scounts, newnsc * sizeof(*ps->scounts));
155 memset(&ps->scounts[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->scounts));
156 ps->nscounts = newnsc;
158 ps->scounts[sid]++;
162 /* Now, match the pattern. */
163 if (!ps->no_pattern_match) {
164 struct pattern p;
165 pattern_match(&ps->pc, ps->ps, &p, b, m);
166 *str = ps->mm ? mm_pattern(ps, *str, &p) : pattern2str(*str, &p);
170 static char *
171 patternscan_play(struct engine *e, struct board *b, struct move *m)
173 struct patternscan *ps = e->data;
175 if (is_resign(m->coord))
176 return NULL;
178 static char str[1048576]; // XXX
179 char *strp = str;
180 *str = 0;
182 /* Scan for supported features. */
183 /* For specifiation of features and their payloads,
184 * please refer to pattern.h. */
185 process_pattern(ps, b, m, &strp);
187 if (ps->competition) {
188 /* Look at other possible moves as well. */
189 for (int f = 0; f < b->flen; f++) {
190 struct move mo = { .coord = b->f[f], .color = m->color };
191 if (is_pass(mo.coord))
192 continue;
193 if (!board_is_valid_move(b, &mo))
194 continue;
195 if (!ps->mm) *strp++ = ' ';
196 process_pattern(ps, b, &mo, &strp);
200 return ps->no_pattern_match ? NULL : str;
203 static coord_t *
204 patternscan_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive)
206 fprintf(stderr, "genmove command not available during patternscan!\n");
207 exit(EXIT_FAILURE);
210 void
211 patternscan_done(struct engine *e)
213 struct patternscan *ps = e->data;
214 if (!ps->gen_spat_dict)
215 return;
217 /* Save newly found patterns. */
219 bool newfile = true;
220 FILE *f = fopen(spatial_dict_filename, "r");
221 if (f) { fclose(f); newfile = false; }
222 f = fopen(spatial_dict_filename, "a");
223 if (newfile)
224 spatial_dict_writeinfo(ps->pc.spat_dict, f);
226 for (int i = ps->loaded_spatials; i < ps->pc.spat_dict->nspatials; i++) {
227 /* By default, threshold is 0 and condition is always true. */
228 assert(i < ps->nscounts && ps->scounts[i] > 0);
229 if (ps->scounts[i] >= ps->spat_threshold
230 || ps->pc.spat_dict->spatials[i].dist == 3)
231 spatial_write(ps->pc.spat_dict, &ps->pc.spat_dict->spatials[i], i, f);
233 fclose(f);
237 struct patternscan *
238 patternscan_state_init(char *arg)
240 struct patternscan *ps = calloc2(1, sizeof(struct patternscan));
241 int xspat = -1;
243 ps->debug_level = 1;
244 ps->pc = DEFAULT_PATTERN_CONFIG;
245 memcpy(&ps->ps, PATTERN_SPEC_MATCH_DEFAULT, sizeof(pattern_spec));
247 if (arg) {
248 char *optspec, *next = arg;
249 while (*next) {
250 optspec = next;
251 next += strcspn(next, ",");
252 if (*next) { *next++ = 0; } else { *next = 0; }
254 char *optname = optspec;
255 char *optval = strchr(optspec, '=');
256 if (optval) *optval++ = 0;
258 if (!strcasecmp(optname, "debug")) {
259 if (optval)
260 ps->debug_level = atoi(optval);
261 else
262 ps->debug_level++;
264 } else if (!strcasecmp(optname, "gen_spat_dict")) {
265 /* If set, re-generate the spatial patterns
266 * dictionary; you need to have a dictionary
267 * of spatial stone configurations in order
268 * to match any spatial features. */
269 ps->gen_spat_dict = !optval || atoi(optval);
271 } else if (!strcasecmp(optname, "no_pattern_match")) {
272 /* If set, do not actually match patterns.
273 * Useful only together with gen_spat_dict
274 * when just building spatial dictionary. */
275 ps->no_pattern_match = !optval || atoi(optval);
277 } else if (!strcasecmp(optname, "spat_threshold") && optval) {
278 /* Minimal number of times new spatial
279 * feature must occur in this run (!) to
280 * be included in the dictionary. Note that
281 * this will produce discontinuous dictionary
282 * that you should renumber. Also note that
283 * 3x3 patterns are always saved. */
284 ps->spat_threshold = atoi(optval);
286 } else if (!strcasecmp(optname, "competition")) {
287 /* In competition mode, first the played
288 * pattern is printed, then all patterns
289 * that could be played (including the played
290 * one). */
291 ps->competition = !optval || atoi(optval);
293 } else if (!strcasecmp(optname, "mm")) {
294 /* Generate output almost suitable for the
295 * Remi Coulom's MM tool, and auxiliar file
296 * "patterns.fdict" with mapping from gamma ids
297 * back to feature,payload pairs. You will need
298 * to post-process the output, substituting
299 * s/\n\n= /#\n/. */
300 ps->mm = !optval || atoi(optval);
302 } else if (!strcasecmp(optname, "xspat") && optval) {
303 /* xspat==0: don't match spatial features
304 * xspat==1: match *only* spatial features */
305 xspat = atoi(optval);
307 /* See pattern.h:pattern_config for description and
308 * pattern.c:DEFAULT_PATTERN_CONFIG for default values
309 * of the following options. */
310 } else if (!strcasecmp(optname, "bdist_max") && optval) {
311 ps->pc.bdist_max = atoi(optval);
312 } else if (!strcasecmp(optname, "spat_min") && optval) {
313 ps->pc.spat_min = atoi(optval);
314 } else if (!strcasecmp(optname, "spat_max") && optval) {
315 ps->pc.spat_max = atoi(optval);
317 } else {
318 fprintf(stderr, "patternscan: Invalid engine argument %s or missing value\n", optname);
319 exit(EXIT_FAILURE);
323 for (int i = 0; i < FEAT_MAX; i++) if ((xspat == 0 && i == FEAT_SPATIAL) || (xspat == 1 && i != FEAT_SPATIAL)) ps->ps[i] = 0;
324 ps->pc.spat_dict = spatial_dict_init(ps->gen_spat_dict);
325 ps->loaded_spatials = ps->pc.spat_dict->nspatials;
327 if (ps->mm)
328 mm_header(ps);
330 return ps;
333 struct engine *
334 engine_patternscan_init(char *arg, struct board *b)
336 struct patternscan *ps = patternscan_state_init(arg);
337 struct engine *e = calloc2(1, sizeof(struct engine));
338 e->name = "PatternScan Engine";
339 e->comment = "You cannot play Pachi with this engine, it is intended for special development use - scanning of games fed to it as GTP streams for various pattern features.";
340 e->genmove = patternscan_genmove;
341 e->notify_play = patternscan_play;
342 e->done = patternscan_done;
343 e->data = ps;
344 // clear_board does not concern us, we like to work over many games
345 e->keep_on_clear = true;
347 return e;