Index: ChangeLog =================================================================== RCS file: /cvsroot/gnugo/gnugo/ChangeLog,v retrieving revision 1.386 diff -u -r1.386 ChangeLog --- ChangeLog 25 Sep 2002 19:52:43 -0000 1.386 +++ ChangeLog 26 Sep 2002 20:01:10 -0000 @@ -2,6 +2,8 @@ -- ChangeLog ------------------------------------------------------------------------- +- persistent cache code moved from reading.c and owl.c to new file + persistent.c - speed optimization in order_moves() in reading.c - speed optimizations in fastlib(), count_common_libs(), find_common_libs() - duplicate lines removed from safety_to_string() Index: engine/Makefile.am =================================================================== RCS file: /cvsroot/gnugo/gnugo/engine/Makefile.am,v retrieving revision 1.11 diff -u -r1.11 Makefile.am --- engine/Makefile.am 18 Aug 2002 02:56:40 -0000 1.11 +++ engine/Makefile.am 26 Sep 2002 20:01:11 -0000 @@ -36,6 +36,7 @@ movelist.c \ optics.c \ owl.c \ + persistent.c \ printutils.c \ readconnect.c \ reading.c \ Index: engine/Makefile.in =================================================================== RCS file: /cvsroot/gnugo/gnugo/engine/Makefile.in,v retrieving revision 1.35 diff -u -r1.35 Makefile.in --- engine/Makefile.in 18 Aug 2002 21:41:02 -0000 1.35 +++ engine/Makefile.in 26 Sep 2002 20:01:11 -0000 @@ -118,6 +118,7 @@ movelist.c \ optics.c \ owl.c \ + persistent.c \ printutils.c \ readconnect.c \ reading.c \ @@ -159,7 +160,7 @@ genmove.$(OBJEXT) globals.$(OBJEXT) handicap.$(OBJEXT) \ hash.$(OBJEXT) influence.$(OBJEXT) interface.$(OBJEXT) \ life.$(OBJEXT) matchpat.$(OBJEXT) move_reasons.$(OBJEXT) \ - movelist.$(OBJEXT) optics.$(OBJEXT) owl.$(OBJEXT) \ + movelist.$(OBJEXT) optics.$(OBJEXT) owl.$(OBJEXT) persistent.$(OBJEXT) \ printutils.$(OBJEXT) readconnect.$(OBJEXT) reading.$(OBJEXT) \ score.$(OBJEXT) semeai.$(OBJEXT) sgfdecide.$(OBJEXT) \ sgffile.$(OBJEXT) shapes.$(OBJEXT) showbord.$(OBJEXT) \ @@ -181,7 +182,7 @@ @AMDEP_TRUE@ $(DEPDIR)/influence.Po $(DEPDIR)/interface.Po \ @AMDEP_TRUE@ $(DEPDIR)/life.Po $(DEPDIR)/matchpat.Po \ @AMDEP_TRUE@ $(DEPDIR)/move_reasons.Po $(DEPDIR)/movelist.Po \ -@AMDEP_TRUE@ $(DEPDIR)/optics.Po $(DEPDIR)/owl.Po \ +@AMDEP_TRUE@ $(DEPDIR)/optics.Po $(DEPDIR)/owl.Po $(DEPDIR)/persistent.Po \ @AMDEP_TRUE@ $(DEPDIR)/printutils.Po $(DEPDIR)/readconnect.Po \ @AMDEP_TRUE@ $(DEPDIR)/reading.Po $(DEPDIR)/score.Po \ @AMDEP_TRUE@ $(DEPDIR)/semeai.Po $(DEPDIR)/sgfdecide.Po \ @@ -250,6 +251,7 @@ @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/movelist.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/optics.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/owl.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/persistent.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/printutils.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/readconnect.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@$(DEPDIR)/reading.Po@am__quote@ Index: engine/cache.h =================================================================== RCS file: /cvsroot/gnugo/gnugo/engine/cache.h,v retrieving revision 1.18 diff -u -r1.18 cache.h --- engine/cache.h 21 May 2002 16:40:53 -0000 1.18 +++ engine/cache.h 26 Sep 2002 20:01:12 -0000 @@ -363,8 +363,19 @@ #define MAX_ROUTINE DISCONNECT #define NUM_ROUTINES (MAX_ROUTINE + 1) -#endif +/* Routine numbers for the persistent owl cache, in addition to + * OWL_ATTACK and OWL_DEFEND defined above. + */ +#define OWL_THREATEN_ATTACK 2 +#define OWL_THREATEN_DEFENSE 3 +#define OWL_DOES_DEFEND 4 +#define OWL_DOES_ATTACK 5 +#define OWL_CONNECTION_DEFENDS 6 +#define OWL_SUBSTANTIAL 7 +#define OWL_CONFIRM_SAFETY 8 + + /* ================================================================ */ /* This has actually nothing to do with caching, but is useful in @@ -404,6 +415,18 @@ save = move; \ savecode = code; \ } + + +/* This too isn't really related to caching but is convenient to have here. + * (Needs to be available in reading.c and persistent.c.) + * + * Minimum number of nodes for which DEBUG_READING_PERFORMANCE reports + * anything. + */ +#define MIN_READING_NODES_TO_REPORT 1000 + + +#endif /* _CACHE_H_ */ /* Index: engine/liberty.h =================================================================== RCS file: /cvsroot/gnugo/gnugo/engine/liberty.h,v retrieving revision 1.118 diff -u -r1.118 liberty.h --- engine/liberty.h 25 Sep 2002 19:39:01 -0000 1.118 +++ engine/liberty.h 26 Sep 2002 20:01:16 -0000 @@ -293,8 +293,25 @@ #define MOVE_ORDERING_PARAMETERS 67 void tune_move_ordering(int params[MOVE_ORDERING_PARAMETERS]); void draw_reading_shadow(void); + + +/* persistent.c */ void purge_persistent_reading_cache(void); +int search_persistent_reading_cache(int routine, int str, int *result, + int *move); +void store_persistent_reading_cache(int routine, int str, int result, + int move, int nodes); void reading_hotspots(float values[BOARDMAX]); +void purge_persistent_owl_cache(void); +int search_persistent_owl_cache(int routine, int apos, int bpos, int cpos, + int *result, int *move, int *move2, + int *certain); +void store_persistent_owl_cache(int routine, int apos, int bpos, int cpos, + int result, int move, int move2, int certain, + int tactical_nodes, char goal[BOARDMAX], + int goal_color); +void owl_hotspots(float values[BOARDMAX]); + /* readconnect.c */ int string_connect(int str1, int str2, int *move); @@ -485,8 +502,6 @@ void owl_analyze_semeai(int apos, int bpos, int *resulta, int *resultb, int *move, int owl); -void purge_persistent_owl_cache(void); -void owl_hotspots(float values[BOARDMAX]); void change_attack(int str, int move, int acode); void change_defense(int str, int move, int dcode); @@ -934,8 +949,6 @@ #endif #define gg_assert(x) ASSERT2(x, -1, -1); - -void draw_active_area(char board[BOARDMAX], int apos); #endif /* _LIBERTY_H_ */ Index: engine/owl.c =================================================================== RCS file: /cvsroot/gnugo/gnugo/engine/owl.c,v retrieving revision 1.106 diff -u -r1.106 owl.c --- engine/owl.c 25 Sep 2002 19:39:01 -0000 1.106 +++ engine/owl.c 26 Sep 2002 20:01:24 -0000 @@ -46,16 +46,6 @@ #include "gnugo.h" -#define MAX_MOVES 3 /* maximum number of branches at each node */ -#define MAX_SEMEAI_MOVES 2 /* semeai branch factor--must be <= MAX_MOVES */ -#define SEMEAI_BRANCH_DEPTH 12 /* Only one move considered below this depths.*/ -#define MAX_SEMEAI_DEPTH 100 /* Don't read below this depth */ -#define MAX_LUNCHES 10 -#define MAX_WORMS 10 /* maximum number of worms in a dragon to be cataloged */ -#define MAX_ESCAPE 3 /* After this many escape moves, owl_determine_life is */ - /* not called */ - - #include #include #include @@ -65,6 +55,15 @@ #include "sgftree.h" #include "gg_utils.h" +#define MAX_MOVES 3 /* maximum number of branches at each node */ +#define MAX_SEMEAI_MOVES 2 /* semeai branch factor--must be <= MAX_MOVES */ +#define SEMEAI_BRANCH_DEPTH 12 /* Only one move considered below this depths.*/ +#define MAX_SEMEAI_DEPTH 100 /* Don't read below this depth */ +#define MAX_LUNCHES 10 +#define MAX_WORMS 10 /* maximum number of worms in a dragon to be cataloged */ +#define MAX_ESCAPE 3 /* After this many escape moves, owl_determine_life is */ + /* not called */ + struct local_owl_data { char goal[BOARDMAX]; char boundary[BOARDMAX]; @@ -129,55 +128,6 @@ void dump_pattern_list(struct matched_patterns_list_data *list); -/* Persistent owl result cache to reuse owl results between moves. */ -struct owl_cache { - int boardsize; - char board[BOARDMAX]; - int movenum; - int tactical_nodes; - int routine; - int apos; /* first input coordinate */ - int bpos; /* second input coordinate */ - int cpos; /* third input coordinate */ - int result; - int result_certain; - int move; /* first result coordinate */ - int move2; /* second result coordinate */ -}; - -#define MAX_OWL_CACHE_SIZE 150 -static struct owl_cache persistent_owl_cache[MAX_OWL_CACHE_SIZE]; -static int persistent_owl_cache_size = 0; - -/* The following two are defined in cache.h */ -/* #define OWL_ATTACK 0 */ -/* #define OWL_DEFEND 1 */ -#define OWL_THREATEN_ATTACK 2 -#define OWL_THREATEN_DEFENSE 3 -#define OWL_DOES_DEFEND 4 -#define OWL_DOES_ATTACK 5 -#define OWL_CONNECTION_DEFENDS 6 -#define OWL_SUBSTANTIAL 7 -#define OWL_CONFIRM_SAFETY 8 - -static int verify_stored_board(char board[BOARDMAX]); -static int search_persistent_owl_cache(int routine, int apos, - int bpos, int cpos, - int *result, int *move, - int *move2, int *certain); -static void store_persistent_owl_cache(int routine, int apos, - int bpos, int cpos, - int result, int move, - int move2, int certain, - int tactical_nodes, - char goal[BOARDMAX], - int goal_color); -static void print_persistent_owl_cache_entry(int k); -static void mark_dragon_hotspot_values(float values[BOARDMAX], int pos, - float contribution, - char active_board[BOARDMAX]); - - static int do_owl_attack(int str, int *move, struct local_owl_data *owl, int komaster, int kom_pos, int escape); @@ -4804,411 +4754,6 @@ owl_stack_pointer--; (*owl)->local_owl_node_counter = nodes; -} - -/*********************** - * Persistent owl cache - ***********************/ - -#define HIGH_LIBERTY_BIT 4 - -void -draw_active_area(char board[BOARDMAX], int apos) -{ - int i, j, ii; - int c = ' '; - int cw = apos==NO_MOVE ? 'O' : 'o'; - int cb = apos==NO_MOVE ? 'X' : 'x'; - - start_draw_board(); - - for (i = 0; i < board_size; i++) { - ii = board_size - i; - fprintf(stderr, "\n%2d", ii); - - for (j = 0; j < board_size; j++) { - int pos = POS(i, j); - if (board[pos] == EMPTY) - c = '.'; - else if (board[pos] == WHITE) - c = cw; - else if (board[pos] == (WHITE | HIGH_LIBERTY_BIT)) - c = 'O'; - else if (board[pos] == BLACK) - c = cb; - else if (board[pos] == (BLACK | HIGH_LIBERTY_BIT)) - c = 'X'; - if (board[pos] == GRAY) - c = '?'; - - if (pos == apos) - fprintf(stderr, "[%c", c); - else if (j > 0 && POS(i, j-1) == apos) - fprintf(stderr, "]%c", c); - else - fprintf(stderr, " %c", c); - } - - fprintf(stderr, " %d", ii); - } - - end_draw_board(); -} - - -/* Returns 1 if the stored board is compatible with the current board, - * 0 otherwise. - */ -static int -verify_stored_board(char p[BOARDMAX]) -{ - int pos; - for (pos = BOARDMIN; pos < BOARDMAX; pos++) { - if (!ON_BOARD(pos)) - continue; - else if (p[pos] == GRAY) - continue; - else if ((p[pos] & 3) != board[pos]) - return 0; - else if (!(p[pos] & HIGH_LIBERTY_BIT)) - continue; - else if (countlib(pos) <= 4) - return 0; - } - - return 1; -} - -/* Remove persistent cache entries which are no longer compatible with - * the board. For efficient use of the cache, it's recommended to call - * this function once per move, before starting the owl reading. It's - * not required for correct operation though. - */ -void -purge_persistent_owl_cache() -{ - int k; - static int last_purge_position_number = -1; - gg_assert(stackp == 0); - - /* Never do this more than once per move. */ - if (last_purge_position_number == position_number) - return; - else - last_purge_position_number = position_number; - - for (k = 0; k < persistent_owl_cache_size; k++) { - if (persistent_owl_cache[k].boardsize != board_size - || !verify_stored_board(persistent_owl_cache[k].board)) { - /* Move the last entry in the cache here and back up the loop - * counter to redo the test at this position in the cache. - */ - if (k < persistent_owl_cache_size - 1) - persistent_owl_cache[k] - = persistent_owl_cache[persistent_owl_cache_size - 1]; - k--; - persistent_owl_cache_size--; - } - } -} - -static int -search_persistent_owl_cache(int routine, int apos, int bpos, int cpos, - int *result, int *move, int *move2, int *certain) -{ - int k; - gg_assert(stackp == 0 || stackp == 1); - - for (k = 0; k < persistent_owl_cache_size; k++) { - if (persistent_owl_cache[k].routine == routine - && persistent_owl_cache[k].apos == apos - && persistent_owl_cache[k].bpos == bpos - && persistent_owl_cache[k].cpos == cpos - && verify_stored_board(persistent_owl_cache[k].board)) { - *result = persistent_owl_cache[k].result; - if (move) *move = persistent_owl_cache[k].move; - if (move2) *move2 = persistent_owl_cache[k].move2; - if (certain) *certain = persistent_owl_cache[k].result_certain; - DEBUG(DEBUG_OWL_PERSISTENT_CACHE, - "persistent owl cache hit: routine %s at %1m result %d\n", - routine_to_string(routine), apos, bpos, cpos, - result_to_string(persistent_owl_cache[k].result)); - return 1; - } - } - return 0; -} - -static void -store_persistent_owl_cache(int routine, int apos, int bpos, int cpos, - int result, int move, int move2, int certain, - int tactical_nodes, - char goal[BOARDMAX], int goal_color) -{ - char active[BOARDMAX]; - int pos; - int k; - int r; - int other = OTHER_COLOR(goal_color); - gg_assert(stackp == 0); - - /* If cache is full, first try to purge it. */ - if (persistent_owl_cache_size == MAX_OWL_CACHE_SIZE) - purge_persistent_owl_cache(); - - /* FIXME: Kick out oldest or least expensive entry instead of giving up. */ - if (persistent_owl_cache_size == MAX_OWL_CACHE_SIZE) { - DEBUG(DEBUG_OWL_PERFORMANCE, "Persistent owl cache full.\n"); - return; - } - - persistent_owl_cache[persistent_owl_cache_size].boardsize = board_size; - persistent_owl_cache[persistent_owl_cache_size].routine = routine; - persistent_owl_cache[persistent_owl_cache_size].apos = apos; - persistent_owl_cache[persistent_owl_cache_size].bpos = bpos; - persistent_owl_cache[persistent_owl_cache_size].cpos = cpos; - persistent_owl_cache[persistent_owl_cache_size].result = result; - persistent_owl_cache[persistent_owl_cache_size].result_certain = certain; - persistent_owl_cache[persistent_owl_cache_size].move = move; - persistent_owl_cache[persistent_owl_cache_size].move2 = move2; - persistent_owl_cache[persistent_owl_cache_size].tactical_nodes = - tactical_nodes; - persistent_owl_cache[persistent_owl_cache_size].movenum = movenum; - - /* Remains to set the board. We let the active area be - * the goal + - * distance four expansion through empty intersections and own stones + - * adjacent opponent strings + - * liberties of adjacent opponent strings with less than five liberties + - * liberties of low liberty neighbors of adjacent opponent strings - * with less than five liberties. - */ - for (pos = BOARDMIN; pos < BOARDMAX; pos++) - if (ON_BOARD(pos)) - active[pos] = (goal[pos] != 0); - - /* Also add critical moves to the active area. */ - if (ON_BOARD1(move)) - active[move] = 1; - - if (ON_BOARD1(move2)) - active[move2] = 1; - - /* Distance four expansion through empty intersections and own stones. */ - for (k = 1; k < 5; k++) { - for (pos = BOARDMIN; pos < BOARDMAX; pos++){ - if (!ON_BOARD(pos) || board[pos] == other || active[pos] != 0) - continue; - if ((ON_BOARD(SOUTH(pos)) && active[SOUTH(pos)] == k) - || (ON_BOARD(WEST(pos)) && active[WEST(pos)] == k) - || (ON_BOARD(NORTH(pos)) && active[NORTH(pos)] == k) - || (ON_BOARD(EAST(pos)) && active[EAST(pos)] == k)) { - if (board[pos] == EMPTY) - active[pos] = k + 1; - else - mark_string(pos, active, (char) (k + 1)); - } - } - } - - /* Adjacent opponent strings. */ - for (pos = BOARDMIN; pos < BOARDMAX; pos++) { - if (board[pos] != other || active[pos] != 0) - continue; - for (r = 0; r < 4; r++) { - int pos2 = pos + delta[r]; - if (ON_BOARD(pos2) && board[pos2] != other && active[pos2] != 0) { - mark_string(pos, active, (char) 1); - break; - } - } - } - - /* Liberties of adjacent opponent strings with less than five liberties + - * liberties of low liberty neighbors of adjacent opponent strings - * with less than five liberties. - */ - for (pos = BOARDMIN; pos < BOARDMAX; pos++) { - if (board[pos] == other && active[pos] != 0 && countlib(pos) < 5) { - int libs[4]; - int liberties = findlib(pos, 4, libs); - int adjs[MAXCHAIN]; - int adj; - for (r = 0; r < liberties; r++) - active[libs[r]] = 1; - - /* Also add liberties of neighbor strings if these are three - * or less. - */ - adj = chainlinks(pos, adjs); - for (r = 0; r < adj; r++) { - if (countlib(adjs[r]) <= 3) { - int s; - liberties = findlib(adjs[r], 3, libs); - for (s = 0; s < liberties; s++) - active[libs[s]] = 1; - } - } - } - } - - for (pos = BOARDMIN; pos < BOARDMAX; pos++) { - int value = board[pos]; - if (!ON_BOARD(pos)) - continue; - if (!active[pos]) - value = GRAY; - else if (IS_STONE(board[pos]) && countlib(pos) > 4) - value |= HIGH_LIBERTY_BIT; - - persistent_owl_cache[persistent_owl_cache_size].board[pos] = value; - } - - if (debug & DEBUG_OWL_PERSISTENT_CACHE) { - gprintf("%o Stored result in cache (entry %d):\n", - persistent_owl_cache_size); - print_persistent_owl_cache_entry(persistent_owl_cache_size); - } - - persistent_owl_cache_size++; -} - - -/* For debugging purposes. */ -static void -print_persistent_owl_cache_entry(int k) -{ - struct owl_cache *entry = &(persistent_owl_cache[k]); - gprintf("%omovenum = %d\n", entry->movenum); - gprintf("%otactical_nodes = %d\n", entry->tactical_nodes); - gprintf("%oroutine = %s\n", routine_to_string(entry->routine)); - gprintf("%o(apos) = %1m\n", entry->apos); - gprintf("%o(bpos) = %1m\n", entry->bpos); - gprintf("%o(cpos) = %1m\n", entry->cpos); - gprintf("%oresult = %d\n", entry->result); - gprintf("%o(move) = %1m\n", entry->move); - gprintf("%o(move2) = %1m\n", entry->move2); - - draw_active_area(entry->board, entry->apos); -} - - -/* Helper for the owl_hotspots() function below. */ -static void -mark_dragon_hotspot_values(float values[BOARDMAX], int dr, - float contribution, char active_board[BOARDMAX]) -{ - int pos; - int k; - ASSERT1(IS_STONE(board[dr]), dr); - for (pos = BOARDMIN; pos < BOARDMAX; pos++) { - if (board[pos] != EMPTY) - continue; - for (k = 0; k < 8; k++) { - int pos2 = pos + delta[k]; - if (IS_STONE(board[pos2]) - && (is_same_dragon(pos2, dr) - || (are_neighbor_dragons(pos2, dr) - && board[pos2] == board[dr])) - && (countlib(pos2) <= 4 - || is_edge_vertex(pos))) { - if (k < 4) { - if (is_same_dragon(pos2, dr)) - values[pos] += contribution; - else - values[pos] += 0.5 * contribution; - break; - } - else { - /* If pos2 = SOUTHWEST(pos), this construction makes - * pos3 = SOUTH(pos) and - * pos4 = WEST(pos) - * and corresponding for all other diagonal movements. - */ - int pos3 = pos + delta[k % 4]; - int pos4 = pos + delta[(k+1) % 4]; - if (board[pos3] == EMPTY || countlib(pos3) <= 2 - || board[pos4] == EMPTY || countlib(pos4) <= 2) - values[pos] += 0.5 * contribution; - break; - } - } - } - /* If not close to the dragon, but within the active area, give - * negative hotspot contribution. - */ - if (k == 8 && active_board[pos] == EMPTY) { - values[pos] -= 0.5 * contribution; - } - } -} - - -/* Based on the entries in the owl cache and their tactical_nodes - * field, compute where the relatively most expensive owl reading is - * going on. - */ -void -owl_hotspots(float values[BOARDMAX]) -{ - int pos; - int k, r; - int libs[MAXLIBS]; - int liberties; - int sum_tactical_nodes = 0; - - /* Don't bother checking out of board. Set values[] to zero there too. */ - for (pos = BOARDMIN; pos < BOARDMAX; pos++) - values[pos] = 0.0; - - /* Compute the total number of tactical nodes for the cached entries. */ - for (k = 0; k < persistent_owl_cache_size; k++) - sum_tactical_nodes += persistent_owl_cache[k].tactical_nodes; - - if (sum_tactical_nodes <= 100) - return; - - /* Loop over all entries and increase the value of vertices adjacent - * to dragons involving expensive owl reading. - */ - for (k = 0; k < persistent_owl_cache_size; k++) { - struct owl_cache *entry = &(persistent_owl_cache[k]); - float contribution = entry->tactical_nodes / (float) sum_tactical_nodes; - if (debug & DEBUG_OWL_PERSISTENT_CACHE) { - gprintf("Owl hotspots: %d %1m %f\n", entry->routine, entry->apos, - contribution); - } - switch (entry->routine) { - case OWL_ATTACK: - case OWL_THREATEN_ATTACK: - case OWL_DEFEND: - case OWL_THREATEN_DEFENSE: - mark_dragon_hotspot_values(values, entry->apos, - contribution, entry->board); - break; - case OWL_DOES_DEFEND: - case OWL_DOES_ATTACK: - case OWL_CONFIRM_SAFETY: - mark_dragon_hotspot_values(values, entry->bpos, - contribution, entry->board); - break; - case OWL_CONNECTION_DEFENDS: - mark_dragon_hotspot_values(values, entry->bpos, - contribution, entry->board); - mark_dragon_hotspot_values(values, entry->cpos, - contribution, entry->board); - break; - case OWL_SUBSTANTIAL: - /* Only consider the liberties of (apos). */ - liberties = findlib(entry->apos, MAXLIBS, libs); - for (r = 0; r < liberties; r++) - values[libs[r]] += contribution; - break; - default: - gg_assert(0); /* Shouldn't happen. */ - break; - } - } } Index: engine/persistent.c =================================================================== RCS file: engine/persistent.c diff -N engine/persistent.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ engine/persistent.c 26 Sep 2002 20:01:26 -0000 @@ -0,0 +1,941 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ + * This is GNU GO, a Go program. Contact gnugo@gnu.org, or see * + * http://www.gnu.org/software/gnugo/ for more information. * + * * + * Copyright 1999, 2000, 2001, 2002 by the Free Software Foundation. * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation - version 2 * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License in file COPYING for more details. * + * * + * You should have received a copy of the GNU General Public * + * License along with this program; if not, write to the Free * + * Software Foundation, Inc., 59 Temple Place - Suite 330, * + * Boston, MA 02111, USA. * +\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* + * The code in this file implements persistent caching. + * + * The idea is that reading results are stored together with an + * "active area", i.e. the part of the board having an effect on the + * reading result. Thus if only moves outside of the active area has + * been played since the result was stored, it can be reused. + * + * The active areas are not known exactly but are estimated + * heuristically. The effects are that too large an active area + * reduces the efficiency of the caching scheme while too small an + * active area may cause an incorrect read result to be retrieved from + * the cache. + * + * Persistent caching has so far been implemented for tactical reading + * and owl reading (with connection reading and semeai reading planned + * for the future). Since different data is stored for the different + * types of reading, similar but different data structures and + * functions are implemented for each cache. + * + * The hotspot functions are intended to locate where the most + * expensive reading of either type is going on. This information can + * be estimated from the contents of the persistent caches since the + * most expensive readings are stored there with full information of + * spent reading nodes, involved strings or dragons, and active areas. + */ + +#include "gnugo.h" + +#include +#include +#include "liberty.h" +#include "cache.h" + + +/* ================================================================ */ +/* Data structures */ +/* ================================================================ */ + +/* Used in active area. */ +#define HIGH_LIBERTY_BIT 4 + +/* Tactical reading cache. */ + +#define MAX_READING_CACHE_DEPTH 5 +#define MAX_READING_CACHE_SIZE 100 + +struct reading_cache { + int boardsize; + char board[BOARDMAX]; + int movenum; + int nodes; + int score; + int remaining_depth; + int routine; /* ATTACK or FIND_DEFENSE */ + int str; /* contested string (origin) */ + int result; + int move; /* attack/defense point */ + int stack[MAX_READING_CACHE_DEPTH]; + int move_color[MAX_READING_CACHE_DEPTH]; +}; + +static struct reading_cache persistent_reading_cache[MAX_READING_CACHE_SIZE]; +static int persistent_reading_cache_size = 0; + + +/* Owl reading cache. */ + +#define MAX_OWL_CACHE_SIZE 150 + +/* Persistent owl result cache to reuse owl results between moves. */ +struct owl_cache { + int boardsize; + char board[BOARDMAX]; + int movenum; + int tactical_nodes; + int routine; + int apos; /* first input coordinate */ + int bpos; /* second input coordinate */ + int cpos; /* third input coordinate */ + int result; + int result_certain; + int move; /* first result coordinate */ + int move2; /* second result coordinate */ +}; + +static struct owl_cache persistent_owl_cache[MAX_OWL_CACHE_SIZE]; +static int persistent_owl_cache_size = 0; + +/* ================================================================ */ +/* Static function declarations */ +/* ================================================================ */ + +/* Common functions. */ +static void draw_active_area(char board[BOARDMAX], int apos); +static int verify_stored_board(char board[BOARDMAX]); + +/* Tactical reading functions. */ +static void print_persistent_reading_cache_entry(int k); +static void mark_string_hotspot_values(float values[BOARDMAX], + int m, int n, float contribution); + +/* Owl functions. */ +static void print_persistent_owl_cache_entry(int k); +static void mark_dragon_hotspot_values(float values[BOARDMAX], int pos, + float contribution, + char active_board[BOARDMAX]); + + +/* ================================================================ */ +/* Common functions */ +/* ================================================================ */ + + +static void +draw_active_area(char board[BOARDMAX], int apos) +{ + int i, j, ii; + int c = ' '; + int cw = apos==NO_MOVE ? 'O' : 'o'; + int cb = apos==NO_MOVE ? 'X' : 'x'; + + start_draw_board(); + + for (i = 0; i < board_size; i++) { + ii = board_size - i; + fprintf(stderr, "\n%2d", ii); + + for (j = 0; j < board_size; j++) { + int pos = POS(i, j); + if (board[pos] == EMPTY) + c = '.'; + else if (board[pos] == WHITE) + c = cw; + else if (board[pos] == (WHITE | HIGH_LIBERTY_BIT)) + c = 'O'; + else if (board[pos] == BLACK) + c = cb; + else if (board[pos] == (BLACK | HIGH_LIBERTY_BIT)) + c = 'X'; + if (board[pos] == GRAY) + c = '?'; + + if (pos == apos) + fprintf(stderr, "[%c", c); + else if (j > 0 && POS(i, j-1) == apos) + fprintf(stderr, "]%c", c); + else + fprintf(stderr, " %c", c); + } + + fprintf(stderr, " %d", ii); + } + + end_draw_board(); +} + + +/* Returns 1 if the stored board is compatible with the current board, + * 0 otherwise. + */ +static int +verify_stored_board(char p[BOARDMAX]) +{ + int pos; + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + if (!ON_BOARD(pos)) + continue; + else if (p[pos] == GRAY) + continue; + else if ((p[pos] & 3) != board[pos]) + return 0; + else if (!(p[pos] & HIGH_LIBERTY_BIT)) + continue; + else if (countlib(pos) <= 4) + return 0; + } + + return 1; +} + + +/* ================================================================ */ +/* Tactical reading functions */ +/* ================================================================ */ + +/* Remove persistent cache entries which are no longer current. */ +void +purge_persistent_reading_cache() +{ + int k; + int r; + static int last_purge_position_number = -1; + gg_assert(stackp == 0); + + /* Never do this more than once per move. */ + if (last_purge_position_number == position_number) + return; + else + last_purge_position_number = position_number; + + for (k = 0; k < persistent_reading_cache_size; k++) { + int played_moves = 0; + int entry_ok = 1; + + if (persistent_reading_cache[k].boardsize != board_size) + entry_ok = 0; + else { + for (r = 0; r < MAX_READING_CACHE_DEPTH; r++) { + int apos = persistent_reading_cache[k].stack[r]; + int color = persistent_reading_cache[k].move_color[r]; + if (apos == 0) + break; + if (board[apos] == EMPTY + && trymove(apos, color, "purge_persistent_reading_cache", 0, + EMPTY, 0)) + played_moves++; + else { + entry_ok = 0; + break; + } + } + } + + if (!entry_ok + || !verify_stored_board(persistent_reading_cache[k].board)) { + /* Move the last entry in the cache here and back up the loop + * counter to redo the test at this position in the cache. + */ + if (0) + gprintf("Purging entry %d from cache.\n", k); + if (k < persistent_reading_cache_size - 1) + persistent_reading_cache[k] + = persistent_reading_cache[persistent_reading_cache_size - 1]; + k--; + persistent_reading_cache_size--; + } + + while (played_moves > 0) { + popgo(); + played_moves--; + } + } +} + + +/* Look for a valid read result in the persistent cache. */ +int +search_persistent_reading_cache(int routine, int str, int *result, int *move) +{ + int k; + int r; + + for (k = 0; k < persistent_reading_cache_size; k++) { + /* Check that everything matches. */ + struct reading_cache *entry = &(persistent_reading_cache[k]); + int apos = 0; + if (entry->routine != routine + || entry->str != str + || entry->remaining_depth < (depth - stackp) + || entry->boardsize != board_size) + continue; + + for (r = 0; r < MAX_READING_CACHE_DEPTH; r++) { + apos = entry->stack[r]; + if (apos == 0 + || (entry->board[apos] != GRAY + && board[apos] != entry->board[apos])) + break; + } + + if (r < MAX_READING_CACHE_DEPTH && apos != 0) + continue; + + if (!verify_stored_board(entry->board)) + continue; + + /* Matched alright. Increase score, fill in the answer, and return. */ + entry->score += entry->nodes; + *result = entry->result; + if (move) + *move = entry->move; + ASSERT1(entry->result == 0 + || entry->move == NO_MOVE + || ON_BOARD(entry->move), + entry->move); + + if ((debug & DEBUG_READING_PERFORMANCE) + && entry->nodes >= MIN_READING_NODES_TO_REPORT) { + if (entry->result != 0) + gprintf("%o%s %1m = %d %1m, cached (%d nodes) ", + routine == ATTACK ? "attack" : "defend", + str, entry->result, entry->move, entry->nodes); + else + gprintf("%o%s %1m = %d, cached (%d nodes) ", + routine == ATTACK ? "attack" : "defend", + str, entry->result, entry->nodes); + dump_stack(); + } + + return 1; + } + + return 0; +} + + +/* Store a new read result in the persistent cache. */ +void +store_persistent_reading_cache(int routine, int str, int result, int move, + int nodes) +{ + char active[BOARDMAX]; + int k; + int r; + int score = nodes; + struct reading_cache *entry; + + ASSERT1(result == 0 || (move == 0) || ON_BOARD(move), move); + + /* Never cache results at too great depth. */ + if (stackp > MAX_READING_CACHE_DEPTH) + return; + + /* If cache is still full, consider kicking out an old entry. */ + if (persistent_reading_cache_size == MAX_READING_CACHE_SIZE) { + int worst_entry = -1; + int worst_score = score; + + for (k = 1; k < persistent_reading_cache_size; k++) { + if (persistent_reading_cache[k].score < worst_score) { + worst_score = persistent_reading_cache[k].score; + worst_entry = k; + } + } + + if (worst_entry != -1) { + /* Move the last entry in the cache here to make space. + */ + if (worst_entry < persistent_reading_cache_size - 1) + persistent_reading_cache[worst_entry] + = persistent_reading_cache[persistent_reading_cache_size - 1]; + persistent_reading_cache_size--; + } + else + return; + } + + entry = &(persistent_reading_cache[persistent_reading_cache_size]); + entry->boardsize = board_size; + entry->movenum = movenum; + entry->nodes = nodes; + entry->score = score; + entry->remaining_depth = depth - stackp; + entry->routine = routine; + entry->str = str; + entry->result = result; + entry->move = move; + + for (r = 0; r < MAX_READING_CACHE_DEPTH; r++) { + if (r < stackp) + get_move_from_stack(r, &(entry->stack[r]), &(entry->move_color[r])); + else { + entry->stack[r] = 0; + entry->move_color[r] = EMPTY; + } + } + + /* Remains to set the board. We let the active area be the contested + * string and reading shadow + adjacent empty and strings + + * neighbors of active area so far + one more expansion from empty + * to empty. + */ + for (k = BOARDMIN; k < BOARDMAX; k++) + active[k] = shadow[k]; + + mark_string(str, active, 1); + + /* To be safe, also add the successful move. */ + if (result != 0 && move != 0) + active[move] = 1; + + /* Add adjacent strings and empty. */ + for (k = BOARDMIN; k < BOARDMAX; k++) { + if (!ON_BOARD(k)) + continue; + if (active[k] != 0) + continue; + if ((ON_BOARD(SOUTH(k)) && active[SOUTH(k)] == 1) + || (ON_BOARD(WEST(k)) && active[WEST(k)] == 1) + || (ON_BOARD(NORTH(k)) && active[NORTH(k)] == 1) + || (ON_BOARD(EAST(k)) && active[EAST(k)] == 1)) { + if (IS_STONE(board[k])) + mark_string(k, active, 2); + else + active[k] = 2; + } + } + + /* Remove invincible strings. No point adding their liberties and + * neighbors. + */ + for (k = BOARDMIN; k < BOARDMAX; k++) { + if (!ON_BOARD(k)) + continue; + if (IS_STONE(board[k]) && worm[k].invincible) + active[k] = 0; + } + + /* Expand empty to empty. */ + for (k = BOARDMIN; k < BOARDMAX; k++) { + if (IS_STONE(board[k]) || active[k] != 0) + continue; + if ((board[SOUTH(k)] == EMPTY && active[SOUTH(k)] == 2) + || (board[WEST(k)] == EMPTY && active[WEST(k)] == 2) + || (board[NORTH(k)] == EMPTY && active[NORTH(k)] == 2) + || (board[EAST(k)] == EMPTY && active[EAST(k)] == 2)) + active[k] = 3; + } + + /* Add neighbors of active area so far. */ + for (k = BOARDMIN; k < BOARDMAX; k++) { + if (!ON_BOARD(k)) + continue; + if (active[k] != 0) + continue; + if ((ON_BOARD(SOUTH(k)) && active[SOUTH(k)] > 0 && active[SOUTH(k)] < 4) + || (ON_BOARD(WEST(k)) && active[WEST(k)] > 0 && active[WEST(k)] < 4) + || (ON_BOARD(NORTH(k)) && active[NORTH(k)] > 0 && active[NORTH(k)] < 4) + || (ON_BOARD(EAST(k)) && active[EAST(k)] > 1 && active[EAST(k)] < 4)) + active[k] = 4; + } + + /* Also add the previously played stones to the active area. */ + for (r = 0; r < stackp; r++) + active[entry->stack[r]] = 5; + + for (k = BOARDMIN; k < BOARDMAX; k++) { + if (!ON_BOARD(k)) + continue; + entry->board[k] = + active[k] != 0 ? board[k] : GRAY; + } + + if (0) { + gprintf("%o Stored result in cache (entry %d):\n", + persistent_reading_cache_size); + print_persistent_reading_cache_entry(persistent_reading_cache_size); + gprintf("%o Reading shadow was:\n"); + draw_reading_shadow(); + } + + persistent_reading_cache_size++; +} + + +/* For debugging purposes. */ +static void +print_persistent_reading_cache_entry(int k) +{ + struct reading_cache *entry = &(persistent_reading_cache[k]); + int r; + gprintf("%oboardsize = %d\n", entry->boardsize); + gprintf("%omovenum = %d\n", entry->movenum); + gprintf("%onodes = %d\n", entry->nodes); + gprintf("%oscore = %d\n", entry->score); + gprintf("%oremaining_depth = %d\n", entry->remaining_depth); + gprintf("%oroutine = %d\n", entry->routine); + gprintf("%ostr = %1m\n", entry->str); + gprintf("%oresult = %d\n", entry->result); + gprintf("%omove = %1m\n", entry->move); + + for (r = 0; r < MAX_READING_CACHE_DEPTH; r++) { + if (entry->stack[r] == 0) + break; + gprintf("%ostack[%d] = %C %1m\n", r, entry->move_color[r], + entry->stack[r]); + } + + draw_active_area(entry->board, NO_MOVE); +} + + +/* Helper for the reading_hotspots() function below. */ +static void +mark_string_hotspot_values(float values[BOARDMAX], + int m, int n, float contribution) +{ + int i, j, k; + + /* If p[m][n] is EMPTY, we just give the contribution to close empty + * vertices. This is a rough simplification. + */ + if (BOARD(m, n) == EMPTY) { + for (i = -1; i <= 1; i++) + for (j = -1; j <= 1; j++) + if (BOARD(m+i, n+j) == EMPTY) + values[POS(m+i, n+j)] += contribution; + return; + } + + /* Otherwise we give contribution to liberties and diagonal + * neighbors of the string at (m, n). + */ + for (i = 0; i < board_size; i++) + for (j = 0; j < board_size; j++) { + if (BOARD(i, j) != EMPTY) + continue; + for (k = 0; k < 8; k++) { + int di = deltai[k]; + int dj = deltaj[k]; + if (IS_STONE(BOARD(i+di, j+dj)) + && same_string(POS(i+di, j+dj), POS(m, n))) { + if (k < 4) { + values[POS(i, j)] += contribution; + break; + } + else { + if (BOARD(i+di, j) == EMPTY || countlib(POS(i+di, j)) <= 2 + || BOARD(i, j+dj) == EMPTY || countlib(POS(i, j+dj)) <= 2) + values[POS(i, j)] += contribution; + break; + } + } + } + } +} + + +/* Based on the entries in the reading cache and their nodes field, + * compute where the relatively most expensive tactical reading is + * going on. + */ +void +reading_hotspots(float values[BOARDMAX]) +{ + int m, n, k; + int sum_nodes = 0; + + for (m = 0; m < board_size; m++) + for (n = 0; n < board_size; n++) + values[POS(m, n)] = 0.0; + + /* Compute the total number of nodes for the cached entries. */ + for (k = 0; k < persistent_reading_cache_size; k++) + sum_nodes += persistent_reading_cache[k].nodes; + + if (sum_nodes <= 100) + return; + + /* Loop over all entries and increase the value of vertices adjacent + * to dragons involving expensive tactical reading. + */ + for (k = 0; k < persistent_reading_cache_size; k++) { + struct reading_cache *entry = &(persistent_reading_cache[k]); + float contribution = entry->nodes / (float) sum_nodes; + if (0) { + gprintf("Reading hotspots: %d %1m %f\n", entry->routine, entry->str, + contribution); + } + switch (entry->routine) { + case ATTACK: + case FIND_DEFENSE: + mark_string_hotspot_values(values, I(entry->str), J(entry->str), + contribution); + break; + default: + gg_assert(0); /* Shouldn't happen. */ + break; + } + } +} + + +/* ================================================================ */ +/* Owl reading functions */ +/* ================================================================ */ + +/* Remove persistent cache entries which are no longer compatible with + * the board. For efficient use of the cache, it's recommended to call + * this function once per move, before starting the owl reading. It's + * not required for correct operation though. + */ +void +purge_persistent_owl_cache() +{ + int k; + static int last_purge_position_number = -1; + gg_assert(stackp == 0); + + /* Never do this more than once per move. */ + if (last_purge_position_number == position_number) + return; + else + last_purge_position_number = position_number; + + for (k = 0; k < persistent_owl_cache_size; k++) { + if (persistent_owl_cache[k].boardsize != board_size + || !verify_stored_board(persistent_owl_cache[k].board)) { + /* Move the last entry in the cache here and back up the loop + * counter to redo the test at this position in the cache. + */ + if (k < persistent_owl_cache_size - 1) + persistent_owl_cache[k] + = persistent_owl_cache[persistent_owl_cache_size - 1]; + k--; + persistent_owl_cache_size--; + } + } +} + +int +search_persistent_owl_cache(int routine, int apos, int bpos, int cpos, + int *result, int *move, int *move2, int *certain) +{ + int k; + gg_assert(stackp == 0 || stackp == 1); + + for (k = 0; k < persistent_owl_cache_size; k++) { + if (persistent_owl_cache[k].routine == routine + && persistent_owl_cache[k].apos == apos + && persistent_owl_cache[k].bpos == bpos + && persistent_owl_cache[k].cpos == cpos + && verify_stored_board(persistent_owl_cache[k].board)) { + *result = persistent_owl_cache[k].result; + if (move) + *move = persistent_owl_cache[k].move; + if (move2) + *move2 = persistent_owl_cache[k].move2; + if (certain) + *certain = persistent_owl_cache[k].result_certain; + + DEBUG(DEBUG_OWL_PERSISTENT_CACHE, + "persistent owl cache hit: routine %s at %1m result %d\n", + routine_to_string(routine), apos, bpos, cpos, + result_to_string(persistent_owl_cache[k].result)); + return 1; + } + } + return 0; +} + +void +store_persistent_owl_cache(int routine, int apos, int bpos, int cpos, + int result, int move, int move2, int certain, + int tactical_nodes, + char goal[BOARDMAX], int goal_color) +{ + char active[BOARDMAX]; + int pos; + int k; + int r; + int other = OTHER_COLOR(goal_color); + gg_assert(stackp == 0); + + /* If cache is full, first try to purge it. */ + if (persistent_owl_cache_size == MAX_OWL_CACHE_SIZE) + purge_persistent_owl_cache(); + + /* FIXME: Kick out oldest or least expensive entry instead of giving up. */ + if (persistent_owl_cache_size == MAX_OWL_CACHE_SIZE) { + DEBUG(DEBUG_OWL_PERFORMANCE, "Persistent owl cache full.\n"); + return; + } + + persistent_owl_cache[persistent_owl_cache_size].boardsize = board_size; + persistent_owl_cache[persistent_owl_cache_size].routine = routine; + persistent_owl_cache[persistent_owl_cache_size].apos = apos; + persistent_owl_cache[persistent_owl_cache_size].bpos = bpos; + persistent_owl_cache[persistent_owl_cache_size].cpos = cpos; + persistent_owl_cache[persistent_owl_cache_size].result = result; + persistent_owl_cache[persistent_owl_cache_size].result_certain = certain; + persistent_owl_cache[persistent_owl_cache_size].move = move; + persistent_owl_cache[persistent_owl_cache_size].move2 = move2; + persistent_owl_cache[persistent_owl_cache_size].tactical_nodes = + tactical_nodes; + persistent_owl_cache[persistent_owl_cache_size].movenum = movenum; + + /* Remains to set the board. We let the active area be + * the goal + + * distance four expansion through empty intersections and own stones + + * adjacent opponent strings + + * liberties of adjacent opponent strings with less than five liberties + + * liberties of low liberty neighbors of adjacent opponent strings + * with less than five liberties. + */ + for (pos = BOARDMIN; pos < BOARDMAX; pos++) + if (ON_BOARD(pos)) + active[pos] = (goal[pos] != 0); + + /* Also add critical moves to the active area. */ + if (ON_BOARD1(move)) + active[move] = 1; + + if (ON_BOARD1(move2)) + active[move2] = 1; + + /* Distance four expansion through empty intersections and own stones. */ + for (k = 1; k < 5; k++) { + for (pos = BOARDMIN; pos < BOARDMAX; pos++){ + if (!ON_BOARD(pos) || board[pos] == other || active[pos] != 0) + continue; + if ((ON_BOARD(SOUTH(pos)) && active[SOUTH(pos)] == k) + || (ON_BOARD(WEST(pos)) && active[WEST(pos)] == k) + || (ON_BOARD(NORTH(pos)) && active[NORTH(pos)] == k) + || (ON_BOARD(EAST(pos)) && active[EAST(pos)] == k)) { + if (board[pos] == EMPTY) + active[pos] = k + 1; + else + mark_string(pos, active, (char) (k + 1)); + } + } + } + + /* Adjacent opponent strings. */ + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + if (board[pos] != other || active[pos] != 0) + continue; + for (r = 0; r < 4; r++) { + int pos2 = pos + delta[r]; + if (ON_BOARD(pos2) && board[pos2] != other && active[pos2] != 0) { + mark_string(pos, active, (char) 1); + break; + } + } + } + + /* Liberties of adjacent opponent strings with less than five liberties + + * liberties of low liberty neighbors of adjacent opponent strings + * with less than five liberties. + */ + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + if (board[pos] == other && active[pos] != 0 && countlib(pos) < 5) { + int libs[4]; + int liberties = findlib(pos, 4, libs); + int adjs[MAXCHAIN]; + int adj; + for (r = 0; r < liberties; r++) + active[libs[r]] = 1; + + /* Also add liberties of neighbor strings if these are three + * or less. + */ + adj = chainlinks(pos, adjs); + for (r = 0; r < adj; r++) { + if (countlib(adjs[r]) <= 3) { + int s; + liberties = findlib(adjs[r], 3, libs); + for (s = 0; s < liberties; s++) + active[libs[s]] = 1; + } + } + } + } + + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + int value = board[pos]; + if (!ON_BOARD(pos)) + continue; + if (!active[pos]) + value = GRAY; + else if (IS_STONE(board[pos]) && countlib(pos) > 4) + value |= HIGH_LIBERTY_BIT; + + persistent_owl_cache[persistent_owl_cache_size].board[pos] = value; + } + + if (debug & DEBUG_OWL_PERSISTENT_CACHE) { + gprintf("%o Stored result in cache (entry %d):\n", + persistent_owl_cache_size); + print_persistent_owl_cache_entry(persistent_owl_cache_size); + } + + persistent_owl_cache_size++; +} + + +/* For debugging purposes. */ +static void +print_persistent_owl_cache_entry(int k) +{ + struct owl_cache *entry = &(persistent_owl_cache[k]); + gprintf("%omovenum = %d\n", entry->movenum); + gprintf("%otactical_nodes = %d\n", entry->tactical_nodes); + gprintf("%oroutine = %s\n", routine_to_string(entry->routine)); + gprintf("%o(apos) = %1m\n", entry->apos); + gprintf("%o(bpos) = %1m\n", entry->bpos); + gprintf("%o(cpos) = %1m\n", entry->cpos); + gprintf("%oresult = %d\n", entry->result); + gprintf("%o(move) = %1m\n", entry->move); + gprintf("%o(move2) = %1m\n", entry->move2); + + draw_active_area(entry->board, entry->apos); +} + + +/* Helper for the owl_hotspots() function below. */ +static void +mark_dragon_hotspot_values(float values[BOARDMAX], int dr, + float contribution, char active_board[BOARDMAX]) +{ + int pos; + int k; + ASSERT1(IS_STONE(board[dr]), dr); + for (pos = BOARDMIN; pos < BOARDMAX; pos++) { + if (board[pos] != EMPTY) + continue; + for (k = 0; k < 8; k++) { + int pos2 = pos + delta[k]; + if (IS_STONE(board[pos2]) + && (is_same_dragon(pos2, dr) + || (are_neighbor_dragons(pos2, dr) + && board[pos2] == board[dr])) + && (countlib(pos2) <= 4 + || is_edge_vertex(pos))) { + if (k < 4) { + if (is_same_dragon(pos2, dr)) + values[pos] += contribution; + else + values[pos] += 0.5 * contribution; + break; + } + else { + /* If pos2 = SOUTHWEST(pos), this construction makes + * pos3 = SOUTH(pos) and + * pos4 = WEST(pos) + * and corresponding for all other diagonal movements. + */ + int pos3 = pos + delta[k % 4]; + int pos4 = pos + delta[(k+1) % 4]; + if (board[pos3] == EMPTY || countlib(pos3) <= 2 + || board[pos4] == EMPTY || countlib(pos4) <= 2) + values[pos] += 0.5 * contribution; + break; + } + } + } + /* If not close to the dragon, but within the active area, give + * negative hotspot contribution. + */ + if (k == 8 && active_board[pos] == EMPTY) { + values[pos] -= 0.5 * contribution; + } + } +} + + +/* Based on the entries in the owl cache and their tactical_nodes + * field, compute where the relatively most expensive owl reading is + * going on. + */ +void +owl_hotspots(float values[BOARDMAX]) +{ + int pos; + int k, r; + int libs[MAXLIBS]; + int liberties; + int sum_tactical_nodes = 0; + + /* Don't bother checking out of board. Set values[] to zero there too. */ + for (pos = BOARDMIN; pos < BOARDMAX; pos++) + values[pos] = 0.0; + + /* Compute the total number of tactical nodes for the cached entries. */ + for (k = 0; k < persistent_owl_cache_size; k++) + sum_tactical_nodes += persistent_owl_cache[k].tactical_nodes; + + if (sum_tactical_nodes <= 100) + return; + + /* Loop over all entries and increase the value of vertices adjacent + * to dragons involving expensive owl reading. + */ + for (k = 0; k < persistent_owl_cache_size; k++) { + struct owl_cache *entry = &(persistent_owl_cache[k]); + float contribution = entry->tactical_nodes / (float) sum_tactical_nodes; + if (debug & DEBUG_OWL_PERSISTENT_CACHE) { + gprintf("Owl hotspots: %d %1m %f\n", entry->routine, entry->apos, + contribution); + } + switch (entry->routine) { + case OWL_ATTACK: + case OWL_THREATEN_ATTACK: + case OWL_DEFEND: + case OWL_THREATEN_DEFENSE: + mark_dragon_hotspot_values(values, entry->apos, + contribution, entry->board); + break; + case OWL_DOES_DEFEND: + case OWL_DOES_ATTACK: + case OWL_CONFIRM_SAFETY: + mark_dragon_hotspot_values(values, entry->bpos, + contribution, entry->board); + break; + case OWL_CONNECTION_DEFENDS: + mark_dragon_hotspot_values(values, entry->bpos, + contribution, entry->board); + mark_dragon_hotspot_values(values, entry->cpos, + contribution, entry->board); + break; + case OWL_SUBSTANTIAL: + /* Only consider the liberties of (apos). */ + liberties = findlib(entry->apos, MAXLIBS, libs); + for (r = 0; r < liberties; r++) + values[libs[r]] += contribution; + break; + default: + gg_assert(0); /* Shouldn't happen. */ + break; + } + } +} + +/* + * Local Variables: + * tab-width: 8 + * c-basic-offset: 2 + * End: + */ Index: engine/reading.c =================================================================== RCS file: /cvsroot/gnugo/gnugo/engine/reading.c,v retrieving revision 1.75 diff -u -r1.75 reading.c --- engine/reading.c 25 Sep 2002 19:52:43 -0000 1.75 +++ engine/reading.c 26 Sep 2002 20:01:34 -0000 @@ -192,46 +192,6 @@ static int in_list(int move, int num_moves, int *moves); -/* ================================================================ */ -/* Persistent reading cache */ -/* ================================================================ */ - - -/* Persistent reading cache to reuse read results between moves and - * within the same move when one or more far away moves have been - * played. - */ - -#define MAX_CACHE_DEPTH 5 - -struct reading_cache { - int boardsize; - char board[BOARDMAX]; - int movenum; - int nodes; - int score; - int remaining_depth; - int routine; /* ATTACK or FIND_DEFENSE */ - int str; /* contested string (origin) */ - int result; - int move; /* attack/defense point */ - int stack[MAX_CACHE_DEPTH]; - int move_color[MAX_CACHE_DEPTH]; -}; - -#define MAX_READING_CACHE_SIZE 100 -static struct reading_cache persistent_reading_cache[MAX_READING_CACHE_SIZE]; -static int persistent_reading_cache_size = 0; - -static int verify_stored_board(char p[BOARDMAX]); -static int search_persistent_reading_cache(int routine, int str, - int *result, int *move); -static void store_persistent_reading_cache(int routine, int str, - int result, int move, int nodes); -static void print_persistent_reading_cache_entry(int k); -static void mark_string_hotspot_values(float values[BOARDMAX], - int m, int n, float contribution); - /* Statistics. */ static int reading_node_counter = 0; static int nodes_when_called = 0; @@ -270,8 +230,6 @@ * which must be answered (the defender makes the first ko capture). */ -#define MIN_NODES_TO_REPORT 1000 - int attack(int str, int *move) { @@ -299,7 +257,8 @@ nodes = reading_node_counter - nodes_when_called; if (debug & DEBUG_READING_PERFORMANCE) { - if (reading_node_counter - nodes_when_called >= MIN_NODES_TO_REPORT) { + if (reading_node_counter - nodes_when_called + >= MIN_READING_NODES_TO_REPORT) { if (result != 0) gprintf("%oattack %1m(%1m) = %d %1M, %d nodes ", str, origin, result, the_move, nodes); @@ -361,7 +320,8 @@ nodes = reading_node_counter - nodes_when_called; if (debug & DEBUG_READING_PERFORMANCE) { - if (reading_node_counter - nodes_when_called >= MIN_NODES_TO_REPORT) { + if (reading_node_counter - nodes_when_called + >= MIN_READING_NODES_TO_REPORT) { if (result != 0) gprintf("%odefend %1m(%1m) = %d %1M, %d nodes ", str, origin, result, the_move, nodes); @@ -5544,7 +5504,7 @@ return reading_node_counter; } -/* ============ Reading shadow and persistent cache =============== */ +/* ============ Reading shadow =============== */ /* Draw the reading shadow, for debugging purposes */ @@ -5581,428 +5541,6 @@ } end_draw_board(); -} - -/* Returns 1 if the stored board is compatible with the current board, - * 0 otherwise. - */ -static int -verify_stored_board(char p[BOARDMAX]) -{ - int k; - for (k = BOARDMIN; k < BOARDMAX; k++) - if (ON_BOARD(k) && p[k] != GRAY && p[k] != board[k]) - return 0; - - return 1; -} - - -/* Remove persistent cache entries which are no longer current. */ -void -purge_persistent_reading_cache() -{ - int k; - int r; - static int last_purge_position_number = -1; - gg_assert(stackp == 0); - - /* Never do this more than once per move. */ - if (last_purge_position_number == position_number) - return; - else - last_purge_position_number = position_number; - - - - for (k = 0; k < persistent_reading_cache_size; k++) { - int played_moves = 0; - int entry_ok = 1; - - if (persistent_reading_cache[k].boardsize != board_size) - entry_ok = 0; - else { - for (r = 0; r < MAX_CACHE_DEPTH; r++) { - int apos = persistent_reading_cache[k].stack[r]; - int color = persistent_reading_cache[k].move_color[r]; - if (apos == 0) - break; - if (board[apos] == EMPTY - && trymove(apos, color, "purge_persistent_reading_cache", 0, - EMPTY, 0)) - played_moves++; - else { - entry_ok = 0; - break; - } - } - } - - if (!entry_ok - || !verify_stored_board(persistent_reading_cache[k].board)) { - /* Move the last entry in the cache here and back up the loop - * counter to redo the test at this position in the cache. - */ - if (0) - gprintf("Purging entry %d from cache.\n", k); - if (k < persistent_reading_cache_size - 1) - persistent_reading_cache[k] - = persistent_reading_cache[persistent_reading_cache_size - 1]; - k--; - persistent_reading_cache_size--; - } - - while (played_moves > 0) { - popgo(); - played_moves--; - } - } -} - - -/* Look for a valid read result in the persistent cache. */ -static int -search_persistent_reading_cache(int routine, int str, int *result, int *move) -{ - int k; - int r; - - for (k = 0; k < persistent_reading_cache_size; k++) { - /* Check that everything matches. */ - struct reading_cache *entry = &(persistent_reading_cache[k]); - int apos = 0; - if (entry->routine != routine - || entry->str != str - || entry->remaining_depth < (depth - stackp) - || entry->boardsize != board_size) - continue; - - for (r = 0; r < MAX_CACHE_DEPTH; r++) { - apos = entry->stack[r]; - if (apos == 0 - || (entry->board[apos] != GRAY - && board[apos] != entry->board[apos])) - break; - } - - if (r < MAX_CACHE_DEPTH && apos != 0) - continue; - - if (!verify_stored_board(entry->board)) - continue; - - /* Matched alright. Increase score, fill in the answer, and return. */ - entry->score += entry->nodes; - *result = entry->result; - if (move) - *move = entry->move; - ASSERT1(entry->result == 0 || (entry->move == 0) || ON_BOARD(entry->move), - entry->move); - - if ((debug & DEBUG_READING_PERFORMANCE) - && entry->nodes >= MIN_NODES_TO_REPORT) { - if (entry->result != 0) - gprintf("%o%s %1m = %d %1m, cached (%d nodes) ", - routine == ATTACK ? "attack" : "defend", - str, entry->result, entry->move, entry->nodes); - else - gprintf("%o%s %1m = %d, cached (%d nodes) ", - routine == ATTACK ? "attack" : "defend", - str, entry->result, entry->nodes); - dump_stack(); - } - - if (0) { - /* Check that the cached value is correct. */ - int result2; - int move2; - - if (routine == ATTACK) - result2 = do_attack(str, &move2, EMPTY, 0); - else - result2 = do_find_defense(str, &move2, EMPTY, 0); - - if (result2 != entry->result - || (result2 != 0 && (move2 != entry->move))) { - gprintf("%oIncorrect cached result %d %1m, should be %d %1m\n", - entry->result, entry->move, result2, move2); - print_persistent_reading_cache_entry(k); - dump_stack(); - showboard(0); - } - } - - return 1; - } - - return 0; -} - - -/* Store a new read result in the persistent cache. */ -static void -store_persistent_reading_cache(int routine, int str, int result, int move, - int nodes) -{ - char active[BOARDMAX]; - int k; - int r; - int score = nodes; - struct reading_cache *entry; - - ASSERT1(result == 0 || (move == 0) || ON_BOARD(move), move); - - /* Never cache results at too great depth. */ - if (stackp > MAX_CACHE_DEPTH) - return; - - /* If cache is still full, consider kicking out an old entry. */ - if (persistent_reading_cache_size == MAX_READING_CACHE_SIZE) { - int worst_entry = -1; - int worst_score = score; - - for (k = 1; k < persistent_reading_cache_size; k++) { - if (persistent_reading_cache[k].score < worst_score) { - worst_score = persistent_reading_cache[k].score; - worst_entry = k; - } - } - - if (worst_entry != -1) { - /* Move the last entry in the cache here to make space. - */ - if (worst_entry < persistent_reading_cache_size - 1) - persistent_reading_cache[worst_entry] - = persistent_reading_cache[persistent_reading_cache_size - 1]; - persistent_reading_cache_size--; - } - else - return; - } - - entry = &(persistent_reading_cache[persistent_reading_cache_size]); - entry->boardsize = board_size; - entry->movenum = movenum; - entry->nodes = nodes; - entry->score = score; - entry->remaining_depth = depth - stackp; - entry->routine = routine; - entry->str = str; - entry->result = result; - entry->move = move; - - for (r = 0; r < MAX_CACHE_DEPTH; r++) { - if (r < stackp) - get_move_from_stack(r, &(entry->stack[r]), &(entry->move_color[r])); - else { - entry->stack[r] = 0; - entry->move_color[r] = EMPTY; - } - } - - /* Remains to set the board. We let the active area be the contested - * string and reading shadow + adjacent empty and strings + - * neighbors of active area so far + one more expansion from empty - * to empty. - */ - for (k = BOARDMIN; k < BOARDMAX; k++) - active[k] = shadow[k]; - - mark_string(str, active, 1); - - /* To be safe, also add the successful move. */ - if (result != 0 && move != 0) - active[move] = 1; - - /* Add adjacent strings and empty. */ - for (k = BOARDMIN; k < BOARDMAX; k++) { - if (!ON_BOARD(k)) - continue; - if (active[k] != 0) - continue; - if ((ON_BOARD(SOUTH(k)) && active[SOUTH(k)] == 1) - || (ON_BOARD(WEST(k)) && active[WEST(k)] == 1) - || (ON_BOARD(NORTH(k)) && active[NORTH(k)] == 1) - || (ON_BOARD(EAST(k)) && active[EAST(k)] == 1)) { - if (IS_STONE(board[k])) - mark_string(k, active, 2); - else - active[k] = 2; - } - } - - /* Remove invincible strings. No point adding their liberties and - * neighbors. - */ - for (k = BOARDMIN; k < BOARDMAX; k++) { - if (!ON_BOARD(k)) - continue; - if (IS_STONE(board[k]) && worm[k].invincible) - active[k] = 0; - } - - /* Expand empty to empty. */ - for (k = BOARDMIN; k < BOARDMAX; k++) { - if (IS_STONE(board[k]) || active[k] != 0) - continue; - if ((board[SOUTH(k)] == EMPTY && active[SOUTH(k)] == 2) - || (board[WEST(k)] == EMPTY && active[WEST(k)] == 2) - || (board[NORTH(k)] == EMPTY && active[NORTH(k)] == 2) - || (board[EAST(k)] == EMPTY && active[EAST(k)] == 2)) - active[k] = 3; - } - - /* Add neighbors of active area so far. */ - for (k = BOARDMIN; k < BOARDMAX; k++) { - if (!ON_BOARD(k)) - continue; - if (active[k] != 0) - continue; - if ((ON_BOARD(SOUTH(k)) && active[SOUTH(k)] > 0 && active[SOUTH(k)] < 4) - || (ON_BOARD(WEST(k)) && active[WEST(k)] > 0 && active[WEST(k)] < 4) - || (ON_BOARD(NORTH(k)) && active[NORTH(k)] > 0 && active[NORTH(k)] < 4) - || (ON_BOARD(EAST(k)) && active[EAST(k)] > 1 && active[EAST(k)] < 4)) - active[k] = 4; - } - - /* Also add the previously played stones to the active area. */ - for (r = 0; r < stackp; r++) - active[entry->stack[r]] = 5; - - for (k = BOARDMIN; k < BOARDMAX; k++) { - if (!ON_BOARD(k)) - continue; - entry->board[k] = - active[k] != 0 ? board[k] : GRAY; - } - - if (0) { - gprintf("%o Stored result in cache (entry %d):\n", - persistent_reading_cache_size); - print_persistent_reading_cache_entry(persistent_reading_cache_size); - gprintf("%o Reading shadow was:\n"); - draw_reading_shadow(); - } - - persistent_reading_cache_size++; -} - - -/* For debugging purposes. */ -static void -print_persistent_reading_cache_entry(int k) -{ - struct reading_cache *entry = &(persistent_reading_cache[k]); - int r; - gprintf("%oboardsize = %d\n", entry->boardsize); - gprintf("%omovenum = %d\n", entry->movenum); - gprintf("%onodes = %d\n", entry->nodes); - gprintf("%oscore = %d\n", entry->score); - gprintf("%oremaining_depth = %d\n", entry->remaining_depth); - gprintf("%oroutine = %d\n", entry->routine); - gprintf("%ostr = %1m\n", entry->str); - gprintf("%oresult = %d\n", entry->result); - gprintf("%omove = %1m\n", entry->move); - - for (r = 0; r < MAX_CACHE_DEPTH; r++) { - if (entry->stack[r] == 0) - break; - gprintf("%ostack[%d] = %C %1m\n", r, entry->move_color[r], - entry->stack[r]); - } - - draw_active_area(entry->board, NO_MOVE); -} - - -/* Helper for the reading_hotspots() function below. */ -static void -mark_string_hotspot_values(float values[BOARDMAX], - int m, int n, float contribution) -{ - int i, j, k; - - /* If p[m][n] is EMPTY, we just give the contribution to close empty - * vertices. This is a rough simplification. - */ - if (BOARD(m, n) == EMPTY) { - for (i = -1; i <= 1; i++) - for (j = -1; j <= 1; j++) - if (BOARD(m+i, n+j) == EMPTY) - values[POS(m+i, n+j)] += contribution; - return; - } - - /* Otherwise we give contribution to liberties and diagonal - * neighbors of the string at (m, n). - */ - for (i = 0; i < board_size; i++) - for (j = 0; j < board_size; j++) { - if (BOARD(i, j) != EMPTY) - continue; - for (k = 0; k < 8; k++) { - int di = deltai[k]; - int dj = deltaj[k]; - if (IS_STONE(BOARD(i+di, j+dj)) - && same_string(POS(i+di, j+dj), POS(m, n))) { - if (k < 4) { - values[POS(i, j)] += contribution; - break; - } - else { - if (BOARD(i+di, j) == EMPTY || countlib(POS(i+di, j)) <= 2 - || BOARD(i, j+dj) == EMPTY || countlib(POS(i, j+dj)) <= 2) - values[POS(i, j)] += contribution; - break; - } - } - } - } -} - - -/* Based on the entries in the reading cache and their nodes field, - * compute where the relatively most expensive tactical reading is - * going on. - */ -void -reading_hotspots(float values[BOARDMAX]) -{ - int m, n, k; - int sum_nodes = 0; - - for (m = 0; m < board_size; m++) - for (n = 0; n < board_size; n++) - values[POS(m, n)] = 0.0; - - /* Compute the total number of nodes for the cached entries. */ - for (k = 0; k < persistent_reading_cache_size; k++) - sum_nodes += persistent_reading_cache[k].nodes; - - if (sum_nodes <= 100) - return; - - /* Loop over all entries and increase the value of vertices adjacent - * to dragons involving expensive tactical reading. - */ - for (k = 0; k < persistent_reading_cache_size; k++) { - struct reading_cache *entry = &(persistent_reading_cache[k]); - float contribution = entry->nodes / (float) sum_nodes; - if (0) { - gprintf("Reading hotspots: %d %1m %f\n", entry->routine, entry->str, - contribution); - } - switch (entry->routine) { - case ATTACK: - case FIND_DEFENSE: - mark_string_hotspot_values(values, I(entry->str), J(entry->str), - contribution); - break; - default: - gg_assert(0); /* Shouldn't happen. */ - break; - } - } }