Replace UCT/montecarlo games=N parameter with -t =N main zzgo parameter
[pachi.git] / patternscan / patternscan.c
blob69d4272184db4b9d08fc1c66bdc6a37509aa1357
1 #include <assert.h>
2 #include <stdio.h>
3 #include <stdlib.h>
5 #include "board.h"
6 #include "engine.h"
7 #include "move.h"
8 #include "patternscan/patternscan.h"
9 #include "pattern.h"
10 #include "patternsp.h"
13 /* Internal engine state. */
14 struct patternscan {
15 int debug_level;
17 struct pattern_config pc;
18 pattern_spec ps;
19 bool competition;
20 bool mm;
22 bool no_pattern_match;
23 bool gen_spat_dict;
24 /* Minimal number of occurences for spatial to be saved. */
25 int spat_threshold;
26 /* Number of loaded spatials; checkpoint for saving new sids
27 * in case gen_spat_dict is enabled. */
28 int loaded_spatials;
30 /* Book-keeping of spatial occurence count. */
31 int nscounts;
32 int *scounts;
36 /* Starting gamma number of each feature. */
37 static int gammaid[FEAT_MAX + MAX_PATTERN_DIST + 1];
38 /* For each spatial id, its gamma value. */
39 static int spatg[65536];
41 /* Print MM-format header - summary of features. Also create patterns.fdict
42 * containing mapping from gamma numbers to feature:payload pairs. */
43 static void
44 mm_header(struct patternscan *ps)
46 FILE *fdict = fopen("patterns.fdict", "w");
47 if (!fdict) {
48 perror("patterns.fdict");
49 exit(EXIT_FAILURE);
52 gammaid[0] = 0;
53 int g = 0;
54 for (int i = 0; i < FEAT_MAX; i++) {
55 if (i == FEAT_SPATIAL) {
56 /* Special handling. */
57 gammaid[i + 1] = gammaid[i];
58 continue;
60 gammaid[i + 1] = gammaid[i] + feature_payloads(&ps->pc, i);
62 for (int p = 0; p < feature_payloads(&ps->pc, i); p++) {
63 struct feature f = { .id = i, .payload = p };
64 char buf[256] = ""; feature2str(buf, &f);
65 fprintf(fdict, "%d %s\n", g++, buf);
67 assert(g == gammaid[i + 1]);
70 /* We need to break down spatials by their radius, since payloads
71 * of single feature must be independent. */
72 for (int d = 0; d <= ps->pc.spat_max - ps->pc.spat_min; d++) {
73 for (int i = 0; i < ps->pc.spat_dict->nspatials; i++) {
74 if (ps->pc.spat_dict->spatials[i].dist != ps->pc.spat_min + d)
75 continue;
76 spatg[i] = g++;
77 struct feature f = { .id = FEAT_SPATIAL, .payload = i };
78 char buf[256] = ""; feature2str(buf, &f);
79 fprintf(fdict, "%d %s\n", spatg[i], buf);
81 gammaid[FEAT_MAX + d + 1] = g;
84 fclose(fdict);
86 int features = FEAT_MAX + ps->pc.spat_max - ps->pc.spat_min + 1;
87 printf("! %d\n", gammaid[features]); // Number of gammas.
88 printf("%d\n", features - 1); // Number of features; we leave out FEAT_SPATIAL record.
89 for (int i = 0; i < FEAT_MAX; i++) {
90 if (i == FEAT_SPATIAL) continue;
91 // Number of gammas per feature.
92 printf("%d %s\n", feature_payloads(&ps->pc, i), feature_name(i));
94 for (int d = 0; d <= ps->pc.spat_max - ps->pc.spat_min; d++) {
95 printf("%d %s.%d\n", gammaid[FEAT_MAX + d + 1] - gammaid[FEAT_MAX + d], feature_name(FEAT_SPATIAL), ps->pc.spat_min + d);
97 printf("!\n");
100 static char *
101 mm_pattern(struct patternscan *ps, char *str, struct pattern *p)
103 for (int i = 0; i < p->n; i++) {
104 if (i > 0) str = stpcpy(str, " ");
105 if (p->f[i].id != FEAT_SPATIAL)
106 str += sprintf(str, "%d", gammaid[p->f[i].id] + p->f[i].payload);
107 else
108 str += sprintf(str, "%d", spatg[p->f[i].payload]);
110 return stpcpy(str, "\n");
114 static void
115 process_pattern(struct patternscan *ps, struct board *b, struct move *m, char **str)
117 /* First, store the spatial configuration in dictionary
118 * if applicable. */
119 if (ps->gen_spat_dict && !is_pass(m->coord)) {
120 struct spatial s;
121 spatial_from_board(&ps->pc, &s, b, m);
122 int dmax = s.dist;
123 for (int d = ps->pc.spat_min; d <= dmax; d++) {
124 s.dist = d;
125 int sid = spatial_dict_put(ps->pc.spat_dict, &s, spatial_hash(0, &s));
126 assert(sid > 0);
127 /* Allocate space in 1024 blocks. */
128 #define SCOUNTS_ALLOC 1024
129 if (sid >= ps->nscounts) {
130 int newnsc = (sid / SCOUNTS_ALLOC + 1) * SCOUNTS_ALLOC;
131 ps->scounts = realloc(ps->scounts, newnsc * sizeof(*ps->scounts));
132 memset(&ps->scounts[ps->nscounts], 0, (newnsc - ps->nscounts) * sizeof(*ps->scounts));
133 ps->nscounts = newnsc;
135 ps->scounts[sid]++;
139 /* Now, match the pattern. */
140 if (!ps->no_pattern_match) {
141 struct pattern p;
142 pattern_match(&ps->pc, ps->ps, &p, b, m);
143 *str = ps->mm ? mm_pattern(ps, *str, &p) : pattern2str(*str, &p);
147 static char *
148 patternscan_play(struct engine *e, struct board *b, struct move *m)
150 struct patternscan *ps = e->data;
152 if (is_resign(m->coord))
153 return NULL;
155 static char str[1048576]; // XXX
156 char *strp = str;
157 *str = 0;
159 /* Scan for supported features. */
160 /* For specifiation of features and their payloads,
161 * please refer to pattern.h. */
162 process_pattern(ps, b, m, &strp);
164 if (ps->competition) {
165 /* Look at other possible moves as well. */
166 for (int f = 0; f < b->flen; f++) {
167 struct move mo = { .coord = b->f[f], .color = m->color };
168 if (is_pass(mo.coord))
169 continue;
170 #if 0
171 /* We want to list again the played move too. This is
172 * required by the MM tool. */
173 if (mo.coord == m->coord)
174 continue;
175 #endif
176 if (!board_is_valid_move(b, &mo))
177 continue;
178 if (!ps->mm) *strp++ = ' ';
179 process_pattern(ps, b, &mo, &strp);
183 return ps->no_pattern_match ? NULL : str;
186 static coord_t *
187 patternscan_genmove(struct engine *e, struct board *b, struct time_info *ti, enum stone color, bool pass_all_alive)
189 fprintf(stderr, "genmove command not available during patternscan!\n");
190 exit(EXIT_FAILURE);
193 void
194 patternscan_done(struct engine *e)
196 struct patternscan *ps = e->data;
197 if (!ps->gen_spat_dict)
198 return;
200 /* Save newly found patterns. */
202 bool newfile = true;
203 FILE *f = fopen(spatial_dict_filename, "r");
204 if (f) { fclose(f); newfile = false; }
205 f = fopen(spatial_dict_filename, "a");
206 if (newfile)
207 spatial_dict_writeinfo(ps->pc.spat_dict, f);
209 for (int i = ps->loaded_spatials; i < ps->pc.spat_dict->nspatials; i++) {
210 /* By default, threshold is 0 and condition is always true. */
211 assert(i < ps->nscounts && ps->scounts[i] > 0);
212 if (ps->scounts[i] >= ps->spat_threshold)
213 spatial_write(&ps->pc.spat_dict->spatials[i], i, f);
215 fclose(f);
219 struct patternscan *
220 patternscan_state_init(char *arg)
222 struct patternscan *ps = calloc(1, sizeof(struct patternscan));
223 int xspat = -1;
225 ps->debug_level = 1;
226 ps->pc = DEFAULT_PATTERN_CONFIG;
227 memcpy(&ps->ps, PATTERN_SPEC_MATCHALL, sizeof(pattern_spec));
229 if (arg) {
230 char *optspec, *next = arg;
231 while (*next) {
232 optspec = next;
233 next += strcspn(next, ",");
234 if (*next) { *next++ = 0; } else { *next = 0; }
236 char *optname = optspec;
237 char *optval = strchr(optspec, '=');
238 if (optval) *optval++ = 0;
240 if (!strcasecmp(optname, "debug")) {
241 if (optval)
242 ps->debug_level = atoi(optval);
243 else
244 ps->debug_level++;
246 } else if (!strcasecmp(optname, "gen_spat_dict")) {
247 /* If set, re-generate the spatial patterns
248 * dictionary; you need to have a dictionary
249 * of spatial stone configurations in order
250 * to match any spatial features. */
251 ps->gen_spat_dict = !optval || atoi(optval);
253 } else if (!strcasecmp(optname, "no_pattern_match")) {
254 /* If set, do not actually match patterns.
255 * Useful only together with gen_spat_dict
256 * when just building spatial dictionary. */
257 ps->no_pattern_match = !optval || atoi(optval);
259 } else if (!strcasecmp(optname, "spat_threshold") && optval) {
260 /* Minimal number of times new spatial
261 * feature must occur in this run (!) to
262 * be included in the dictionary. Note that
263 * this will produce discontinuous dictionary
264 * that you should renumber. */
265 ps->spat_threshold = atoi(optval);
267 } else if (!strcasecmp(optname, "competition")) {
268 /* In competition mode, first the played
269 * pattern is printed, then all patterns
270 * that could be played (including the played
271 * one). */
272 ps->competition = !optval || atoi(optval);
274 } else if (!strcasecmp(optname, "matchfast")) {
275 /* Limit the matched features only to the
276 * set used in MC simulations. */
277 ps->pc = FAST_PATTERN_CONFIG;
278 memcpy(&ps->ps, PATTERN_SPEC_MATCHFAST, sizeof(pattern_spec));
280 } else if (!strcasecmp(optname, "mm")) {
281 /* Generate output almost suitable for the
282 * Remi Coulom's MM tool, and auxiliar file
283 * "patterns.fdict" with mapping from gamma ids
284 * back to feature,payload pairs. You will need
285 * to post-process the output, substituting
286 * s/\n\n= /#\n/. */
287 ps->mm = !optval || atoi(optval);
289 } else if (!strcasecmp(optname, "xspat") && optval) {
290 /* xspat==0: don't match spatial features
291 * xspat==1: match *only* spatial features */
292 xspat = atoi(optval);
294 /* See pattern.h:pattern_config for description and
295 * pattern.c:DEFAULT_PATTERN_CONFIG for default values
296 * of the following options. */
297 } else if (!strcasecmp(optname, "spat_min") && optval) {
298 ps->pc.spat_min = atoi(optval);
299 } else if (!strcasecmp(optname, "spat_max") && optval) {
300 ps->pc.spat_max = atoi(optval);
301 } else if (!strcasecmp(optname, "bdist_max") && optval) {
302 ps->pc.bdist_max = atoi(optval);
303 } else if (!strcasecmp(optname, "ldist_min") && optval) {
304 ps->pc.ldist_min = atoi(optval);
305 } else if (!strcasecmp(optname, "ldist_max") && optval) {
306 ps->pc.ldist_max = atoi(optval);
307 } else if (!strcasecmp(optname, "mcsims") && optval) {
308 ps->pc.mcsims = atoi(optval);
310 } else {
311 fprintf(stderr, "patternscan: Invalid engine argument %s or missing value\n", optname);
312 exit(EXIT_FAILURE);
316 for (int i = 0; i < FEAT_MAX; i++) if ((xspat == 0 && i == FEAT_SPATIAL) || (xspat == 1 && i != FEAT_SPATIAL)) ps->ps[i] = 0;
317 ps->pc.spat_dict = spatial_dict_init(ps->gen_spat_dict);
318 ps->loaded_spatials = ps->pc.spat_dict->nspatials;
320 if (ps->mm)
321 mm_header(ps);
323 return ps;
326 struct engine *
327 engine_patternscan_init(char *arg, struct board *b)
329 struct patternscan *ps = patternscan_state_init(arg);
330 struct engine *e = calloc(1, sizeof(struct engine));
331 e->name = "PatternScan Engine";
332 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.";
333 e->genmove = patternscan_genmove;
334 e->notify_play = patternscan_play;
335 e->done = patternscan_done;
336 e->data = ps;
337 // clear_board does not concern us, we like to work over many games
338 e->keep_on_clear = true;
340 return e;