diff --git a/.gitignore b/.gitignore index dd51bc1ac..87d92e1af 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ cpp/main cpp/maincuda cpp/mainopencl cpp/katago -cpp/configs +# cpp/configs cpp/evalsgf cpp/runmatch.sh cpp/runmatchauto.sh diff --git a/cpp/configs/gatekeeper1.cfg b/cpp/configs/gatekeeper1.cfg new file mode 100644 index 000000000..b4334cb41 --- /dev/null +++ b/cpp/configs/gatekeeper1.cfg @@ -0,0 +1,100 @@ + +# Logs------------------------------------------------------------------------------------ + +logSearchInfo = false +logMoves = false +logGamesEvery = 10 +logToStdout = true + +# Fancy game selfplay settings-------------------------------------------------------------------- + +# startPosesProb = 0.0 # Play this proportion of games starting from SGF positions +# startPosesFromSgfDir = DIRECTORYPATH # Load SGFs from this dir +# startPosesLoadProb = 1.0 # Only load each position from each SGF with this chance (save memory) +# startPosesTurnWeightLambda = 0 # 0 = equal weight 0.01 = decrease probability by 1% per turn -0.01 = increase probability by 1% per turn. + +# Match----------------------------------------------------------------------------------- + +numGameThreads = 50 +maxMovesPerGame = 1600 +numGamesPerGating = 50 + +allowResignation = true +resignThreshold = -0.95 +resignConsecTurns = 5 + +# Disabled, since we're not using any root noise and such +# Could have a slight weirdness on rootEndingBonusPoints, but shouldn't be a big deal. +# clearBotBeforeSearch = true + +# Rules------------------------------------------------------------------------------------ + +koRules = POSITIONAL +scoringRules = AREA,TERRITORY +taxRules = NONE +multiStoneSuicideLegals = false +hasButtons = false + +bSizes = 19 +bSizeRelProbs = 30 +allowRectangleProb = 0.00 # Play game on rectangular board with this probability + +komiAuto = True # Automatically adjust komi to what the neural nets think are fair +#komiMean = 7.5 # Specify explicit komi +handicapProb = 0.0 # Probability of handicap game +handicapCompensateKomiProb = 1.0 # Probability of compensating komi to fair during handicap game +# numExtraBlackFixed = 3 # When playing handicap games, always use exactly this many extra black moves + +# Search limits----------------------------------------------------------------------------------- +maxVisits = 600 +numSearchThreads = 6 + +# GPU Settings------------------------------------------------------------------------------- + +nnMaxBatchSize = 128 +nnCacheSizePowerOfTwo = 21 +nnMutexPoolSizePowerOfTwo = 15 +numNNServerThreadsPerModel = 1 +nnRandomize = true + +# CUDA GPU settings-------------------------------------- +# cudaDeviceToUse = 0 #use device 0 for all server threads (numNNServerThreadsPerModel) unless otherwise specified per-model or per-thread-per-model +# cudaDeviceToUseModel0 = 3 #use device 3 for model 0 for all threads unless otherwise specified per-thread for this model +# cudaDeviceToUseModel1 = 2 #use device 2 for model 1 for all threads unless otherwise specified per-thread for this model +# cudaDeviceToUseModel0Thread0 = 3 #use device 3 for model 0, server thread 0 +# cudaDeviceToUseModel0Thread1 = 2 #use device 2 for model 0, server thread 1 + +cudaUseFP16 = auto +cudaUseNHWC = auto + +# Root move selection and biases------------------------------------------------------------------------------ + +chosenMoveTemperatureEarly = 0.5 +chosenMoveTemperatureHalflife = 19 +chosenMoveTemperature = 0.2 +chosenMoveSubtract = 0 +chosenMovePrune = 1 + +useLcbForSelection = true +lcbStdevs = 5.0 +minVisitPropForLCB = 0.15 + +# Internal params------------------------------------------------------------------------------ + +winLossUtilityFactor = 1.0 +staticScoreUtilityFactor = 0.00 +dynamicScoreUtilityFactor = 0.25 +dynamicScoreCenterZeroWeight = 0.25 +dynamicScoreCenterScale = 0.50 +noResultUtilityForWhite = 0.0 +drawEquivalentWinsForWhite = 0.5 + +rootEndingBonusPoints = 0.5 +rootPruneUselessMoves = true + +cpuctExploration = 1.1 +cpuctExplorationLog = 0.0 +fpuReductionMax = 0.2 +rootFpuReductionMax = 0.1 + +numVirtualLossesPerThread = 1 diff --git a/cpp/configs/gtp_example.cfg b/cpp/configs/gtp_example.cfg index 4003786f2..cdf4c732b 100644 --- a/cpp/configs/gtp_example.cfg +++ b/cpp/configs/gtp_example.cfg @@ -89,30 +89,31 @@ logToStderr = false # Some other legal values are: "chinese", "japanese", "korean", "aga", "chinese-ogs", "new-zealand". # KataGo does not claim to exactly match any particular human ruleset, but KataGo will try to behave # as closely as possible given the rules it has implemented. -rules = tromp-taylor +# rules = tromp-taylor +rule = chinese # Use the below instead to specify an arbitrary combination of individual rules. # koRule = SIMPLE # Simple ko rules (triple ko = no result) -# koRule = POSITIONAL # Positional superko +koRule = POSITIONAL # Positional superko # koRule = SITUATIONAL # Situational superko -# scoringRule = AREA # Area scoring +scoringRule = AREA # Area scoring # scoringRule = TERRITORY # Territory scoring (uses a sort of special computer-friendly territory ruleset) -# taxRule = NONE # All surrounded empty points are scored +taxRule = NONE # All surrounded empty points are scored # taxRule = SEKI # Eyes in seki do NOT count as points # taxRule = ALL # All groups are taxed up to 2 points for the two eyes needed to live -# multiStoneSuicideLegal = true # Is multiple-stone suicide legal? (Single-stone suicide is always illegal). +multiStoneSuicideLegal = false # Is multiple-stone suicide legal? (Single-stone suicide is always illegal). -# hasButton = false # Set to true when area scoring to award 0.5 points to the first pass. +hasButton = false # Set to true when area scoring to award 0.5 points to the first pass. # friendlyPassOk = true # Set to true except for computer rulesets that requires capturing all stones before passing. # whiteHandicapBonus = 0 # In handicap games, give white no compensation for black's handicap stones (Tromp-taylor, NZ, JP) # whiteHandicapBonus = N-1 # In handicap games, give white N-1 points for black's handicap stones (AGA) -# whiteHandicapBonus = N # In handicap games, give white N points for black's handicap stones (Chinese) +whiteHandicapBonus = N # In handicap games, give white N points for black's handicap stones (Chinese) # Uncomment and change to adjust what board size KataGo uses upon startup by default if GTP doesn't specify. # defaultBoardSize = 19 @@ -126,7 +127,7 @@ rules = tromp-taylor # Resignation occurs if for at least resignConsecTurns in a row, # the winLossUtility (which is on a [-1,1] scale) is below resignThreshold. allowResignation = true -resignThreshold = -0.90 +resignThreshold = -0.95 resignConsecTurns = 3 # Uncomment to make katago not resign close games, behind by fewer than this many points # resignMinScoreDifference = 10 @@ -202,7 +203,7 @@ resignConsecTurns = 3 # faster than the specified max if GTP tells it that it is playing under a clock as well in the current game. # If provided, limit maximum number of root visits per search to this much. (With tree reuse, visits do count earlier search) -maxVisits = 500 +maxVisits = 600 # If provided, limit maximum number of new playouts per search to this much. (With tree reuse, playouts do not count earlier search) # maxPlayouts = 300 # If provided, cap search time at this many seconds. @@ -210,7 +211,7 @@ maxVisits = 500 # Ponder on the opponent's turn? ponderingEnabled = false -maxTimePondering = 60 # Maximum time to ponder, in seconds. Comment out to make unlimited. +# maxTimePondering = 60 # Maximum time to ponder, in seconds. Comment out to make unlimited. # Note: you can set "maxVisitsPondering" or "maxPlayoutsPondering" too. # Approx number of seconds to buffer for lag for GTP time controls - will move a bit faster assuming there is this much lag per move. @@ -223,8 +224,8 @@ numSearchThreads = 6 searchFactorAfterOnePass = 0.50 searchFactorAfterTwoPass = 0.25 # Play a little faster if super-winning, for friendliness -searchFactorWhenWinning = 0.40 -searchFactorWhenWinningThreshold = 0.95 +# searchFactorWhenWinning = 0.40 +# searchFactorWhenWinningThreshold = 0.95 # GPU Settings------------------------------------------------------------------------------- @@ -232,17 +233,17 @@ searchFactorWhenWinningThreshold = 0.95 # The default value here is roughly equal to numSearchThreads, but you can specify it manually # if you are running out of memory, or if you are using multiple GPUs that expect to split # up the work. -# nnMaxBatchSize = +# nnMaxBatchSize = 128 # Cache up to (2 ** this) many neural net evaluations in case of transpositions in the tree. # Uncomment and edit to change if you want to adjust a major component of KataGo's RAM usage. -# nnCacheSizePowerOfTwo = 20 +nnCacheSizePowerOfTwo = 21 # Size of mutex pool for nnCache is (2 ** this). -# nnMutexPoolSizePowerOfTwo = 16 +nnMutexPoolSizePowerOfTwo = 16 # Randomize board orientation when running neural net evals? Uncomment and set to false to disable. -# nnRandomize = true +nnRandomize = true # If provided, force usage of a specific seed for nnRandomize instead of randomizing. # nnRandSeed = abcdefg @@ -462,7 +463,3 @@ searchFactorWhenWinningThreshold = 0.95 # avoidSgf2PatternLambda = ... # avoidSgf2PatternAllowedNames = ... # avoidSgf2PatternMinTurnNumber = ... - - - - diff --git a/cpp/configs/gtp_example2.cfg b/cpp/configs/gtp_example2.cfg new file mode 100644 index 000000000..4e6c1913c --- /dev/null +++ b/cpp/configs/gtp_example2.cfg @@ -0,0 +1,465 @@ +# Config for KataGo C++ GTP engine, i.e. "./katago.exe gtp" + +# RUNNING ON AN ONLINE SERVER OR IN A REAL TOURNAMENT OR MATCH: +# If you plan to do so, you may want to read through the "Rules" section +# below carefully for proper handling of komi and handicap games and end-of-game cleanup +# and various other details. + +# NOTES ABOUT PERFORMANCE AND MEMORY USAGE: +# You will likely want to tune one or more the following: +# +# numSearchThreads: +# The number of CPU threads to use. If your GPU is powerful, it can actually be much higher than +# the number of cores on your processor because you will need many threads to feed large enough +# batches to make good use of the GPU. +# +# The "./katago benchmark" command can help you tune this parameter, as well as to test out the effect +# of changes to any of the other parameters below! +# +# nnCacheSizePowerOfTwo: +# This controls the NN Cache size, which is the primary RAM/memory use. +# Increase this if you don't mind the memory use and want better performance for searches with +# tens of thousands of visits or more. Decrease this if you want to limit memory usage. +# +# If you're someone who is happy to do a bit of math - each neural net entry takes very +# approximately 1.5KB, except when using whole-board ownership/territory visualizations, each +# entry will take very approximately 3KB. The number of entries is (2 ** nnCacheSizePowerOfTwo), +# for example 2 ** 18 = 262144. +# +# OTHER NOTES: +# If you have more than one GPU, take a look at "OpenCL GPU settings" or "CUDA GPU settings" below. +# +# If using OpenCL, you will want to verify that KataGo is picking up the correct device! +# (e.g. some systems may have both an Intel CPU OpenCL and GPU OpenCL, if KataGo appears to pick +# the wrong one, you correct this by specifying "openclGpuToUse" below). +# +# You may also want to adjust "maxVisits", "ponderingEnabled", "resignThreshold", and possibly +# other parameters depending on your intended usage. +# +# ---------------------------------------------------------------------------------------- + +# For the `katago gtp` command, ALL of THE BELOW VALUES MAY BE SET OR OVERRIDDEN if desired via +# the command line arguments: +# -override-config KEY=VALUE,KEY=VALUE,... + +# Logs and files-------------------------------------------------------------------------- + +# Where to output log? +logDir = gtp_logs # Each run of KataGo will log to a separate file in this dir +# logDirDated = gtp_logs # Use this instead of logDir to also write separate dated subdirs +# logFile = gtp.log # Use this instead of logDir to just specify a single file directly + +# Logging options +logAllGTPCommunication = true +logSearchInfo = true +logToStderr = false + +# KataGo will display some info to stderr on GTP startup +# Uncomment this to suppress that and remain silent +# startupPrintMessageToStderr = false + +# Chat some stuff to stderr, for use in things like malkovich chat to OGS. +# ogsChatToStderr = true + +# Optionally override where KataGo will attempt to save things like openCLTuner files and other cached data. +# homeDataDir = DIRECTORY + +# Analysis------------------------------------------------------------------------------------ + +# Configure the maximum length of analysis printed out by lz-analyze and other places. +# Controls the number of moves after the first move in a variation. +# analysisPVLen = 15 + +# Report winrates for chat and analysis as (BLACK|WHITE|SIDETOMOVE). +# Default is SIDETOMOVE, which is what tools that use LZ probably also expect +# reportAnalysisWinratesAs = SIDETOMOVE + +# Larger values will make KataGo explore the top move(s) less deeply and accurately, +# but explore and give evaluations to a greater variety of moves, for analysis (does NOT affect play). +# Defaults to 0.04. +# An extreme value like 1 will distribute many playouts across every move on the board, even very bad moves. +# analysisWideRootNoise = 0.04 + + +# Default rules------------------------------------------------------------------------------------ +# See https://lightvector.github.io/KataGo/rules.html for a description of the rules. +# These rules are defaults and can be changed mid-run by several custom GTP commands. +# See https://github.com/lightvector/KataGo/blob/master/docs/GTP_Extensions.md for those commands. + +# Some other legal values are: "chinese", "japanese", "korean", "aga", "chinese-ogs", "new-zealand". +# KataGo does not claim to exactly match any particular human ruleset, but KataGo will try to behave +# as closely as possible given the rules it has implemented. +# rules = tromp-taylor +rule = chinese + +# Use the below instead to specify an arbitrary combination of individual rules. + +# koRule = SIMPLE # Simple ko rules (triple ko = no result) +koRule = POSITIONAL # Positional superko +# koRule = SITUATIONAL # Situational superko + +scoringRule = AREA # Area scoring +# scoringRule = TERRITORY # Territory scoring (uses a sort of special computer-friendly territory ruleset) + +taxRule = NONE # All surrounded empty points are scored +# taxRule = SEKI # Eyes in seki do NOT count as points +# taxRule = ALL # All groups are taxed up to 2 points for the two eyes needed to live + +multiStoneSuicideLegal = false # Is multiple-stone suicide legal? (Single-stone suicide is always illegal). + +hasButton = false # Set to true when area scoring to award 0.5 points to the first pass. + +# friendlyPassOk = true # Set to true except for computer rulesets that requires capturing all stones before passing. + +# whiteHandicapBonus = 0 # In handicap games, give white no compensation for black's handicap stones (Tromp-taylor, NZ, JP) +# whiteHandicapBonus = N-1 # In handicap games, give white N-1 points for black's handicap stones (AGA) +whiteHandicapBonus = N # In handicap games, give white N points for black's handicap stones (Chinese) + +# Uncomment and change to adjust what board size KataGo uses upon startup by default if GTP doesn't specify. +# defaultBoardSize = 19 +# Specify this to force a particular komi, EVEN if the GUI or GTP controller tries to set a different one +# ignoreGTPAndForceKomi = 7 + +# Bot behavior--------------------------------------------------------------------------------------- + +# Resignation ------------- + +# Resignation occurs if for at least resignConsecTurns in a row, +# the winLossUtility (which is on a [-1,1] scale) is below resignThreshold. +allowResignation = true +resignThreshold = -0.95 +resignConsecTurns = 3 +# Uncomment to make katago not resign close games, behind by fewer than this many points +# resignMinScoreDifference = 10 + +# Handicap ------------- + +# Assume that if black makes many moves in a row right at the start of the game, then the game is a handicap game. +# This is necessary on some servers and for some GUIs and also when initializing from many SGF files, which may +# set up a handicap game using repeated GTP "play" commands for black rather than GTP "place_free_handicap" commands. +# However, it may also lead to incorrect understanding of komi if whiteHandicapBonus is used and a server does NOT +# have such a practice. +# Defaults to true! Uncomment and set to false to disable this behavior. +# assumeMultipleStartingBlackMovesAreHandicap = true + +# Makes katago dynamically adjust in handicap or altered-komi games to assume based on those game settings that it +# must be stronger or weaker than the opponent and to play accordingly. Greatly improves handicap +# strength by biasing winrates and scores to favor appropriate safe/aggressive play. +# Does NOT affect analysis (lz-analyze, kata-analyze, used by programs like Lizzie) so analysis remains unbiased. +# Uncomment and set this to 0 to disable this and make KataGo play the same always. +# dynamicPlayoutDoublingAdvantageCapPerOppLead = 0.045 + +# Instead of a dynamic level, you can uncomment this and set this to a value from -3.0 to 3.0 to set KataGo's aggression to a FIXED level. +# DOES affect analysis tools (lz-analyze, kata-analyze, used by programs like Lizzie). +# Negative makes KataGo behave as if it is much weaker than the opponent, preferring to play defensively. +# Positive makes KataGo behave as if it is much stronger than the opponent, prefering to play aggressively or even overplay slightly. +# If this and "dynamicPlayoutDoublingAdvantageCapPerOppLead" are BOTH set then dynamic will be used for all games and this fixed +# value will be used for analysis tools. +# playoutDoublingAdvantage = 0.0 + +# Uncommenting one of these will enforce that the FIXED playoutDoublingAdvantage will only apply when KataGo plays the specified color +# and will be negated when playing the opposite color. +# playoutDoublingAdvantagePla = BLACK +# playoutDoublingAdvantagePla = WHITE + +# Passing and cleanup ------------- + +# Make the bot never assume that its pass will end the game, even if passing would end and "win" under Tromp-Taylor rules. +# Usually this is a good idea when using it for analysis or playing on servers where scoring may be implemented non-tromp-taylorly. +# Defaults to true! Uncomment and set to false to disable this. +# conservativePass = true + +# When using territory scoring, self-play games continue beyond two passes with special cleanup +# rules that may be confusing for human players. This option prevents the special cleanup phases from being +# reachable when using the bot for GTP play. +# Defaults to true! Uncomment and set to false if you want KataGo to be able to enter special cleanup. +# For example, if you are testing it against itself, or against another bot that has precisely implemented the rules +# documented at https://lightvector.github.io/KataGo/rules.html +# preventCleanupPhase = true + +# Misc Behavior -------------------- + +# If the board is symmetric, search only one copy of each equivalent move. Attempts to also account for ko/superko, will not theoretically perfect for superko. +# Uncomment and set to false to disable this. +# rootSymmetryPruning = true + +# Uncomment and set to true to make KataGo avoid a particular joseki that some KataGo nets misevaluate, +# and also to improve opening diversity versus some particular other bots that like to play it all the time. +# avoidMYTDaggerHack = false + +# Have KataGo mildly prefer to avoid playing the same joseki in every corner of the board. +# Uncomment to set to a specific value. Otherwise, defaults to 0 in even games, and to 0.005 in handicap games. +# See also the Avoid SGF mechanism at the bottom of this config. +# avoidRepeatedPatternUtility = 0.0 + +# Experimental logic to make KataGo fight a bit against mirror Go even with unfavorable komi. +# Enabled by default for GTP play, disabled for GTP analysis (i.e lizzie) and analysis engine. +# Uncomment and set to true to enable it for analysis, or false to disable it fully. +# antiMirror = true + +# Search limits----------------------------------------------------------------------------------- + +# For all of "maxVisits", "maxPlayouts", "maxTime", search will still try to follow GTP time controls and may make a move +# faster than the specified max if GTP tells it that it is playing under a clock as well in the current game. + +# If provided, limit maximum number of root visits per search to this much. (With tree reuse, visits do count earlier search) +# maxVisits = 12000 +# If provided, limit maximum number of new playouts per search to this much. (With tree reuse, playouts do not count earlier search) +# maxPlayouts = 300 +# If provided, cap search time at this many seconds. +maxTime = 15 + +# Ponder on the opponent's turn? +ponderingEnabled = true +# maxTimePondering = 60 # Maximum time to ponder, in seconds. Comment out to make unlimited. +# Note: you can set "maxVisitsPondering" or "maxPlayoutsPondering" too. + +# Approx number of seconds to buffer for lag for GTP time controls - will move a bit faster assuming there is this much lag per move. +lagBuffer = 1.0 + +# Number of threads to use in search +numSearchThreads = 40 + +# Play a little faster if the opponent is passing, for friendliness +searchFactorAfterOnePass = 0.50 +searchFactorAfterTwoPass = 0.25 +# Play a little faster if super-winning, for friendliness +# searchFactorWhenWinning = 0.40 +# searchFactorWhenWinningThreshold = 0.95 + +# GPU Settings------------------------------------------------------------------------------- + +# Maximum number of positions to send to a single GPU at once. +# The default value here is roughly equal to numSearchThreads, but you can specify it manually +# if you are running out of memory, or if you are using multiple GPUs that expect to split +# up the work. +# nnMaxBatchSize = 128 + +# Cache up to (2 ** this) many neural net evaluations in case of transpositions in the tree. +# Uncomment and edit to change if you want to adjust a major component of KataGo's RAM usage. +nnCacheSizePowerOfTwo = 21 + +# Size of mutex pool for nnCache is (2 ** this). +nnMutexPoolSizePowerOfTwo = 16 + +# Randomize board orientation when running neural net evals? Uncomment and set to false to disable. +nnRandomize = true +# If provided, force usage of a specific seed for nnRandomize instead of randomizing. +# nnRandSeed = abcdefg + +# TO USE MULTIPLE GPUS: +# Set this to the number of GPUs you have and/or would like to use. +# **AND** if it is more than 1, uncomment the appropriate CUDA or OpenCL section below. +# numNNServerThreadsPerModel = 1 + + +# TENSORRT GPU settings-------------------------------------- +# These only apply when using the TENSORRT version of KataGo. + +# IF USING ONE GPU: optionally uncomment and change this if the GPU you want to use turns out to be not device 0 +# trtDeviceToUse = 0 + +# IF USING TWO GPUS: Uncomment these two lines (AND set numNNServerThreadsPerModel above): +# trtDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0 +# trtDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1 + +# IF USING THREE GPUS: Uncomment these three lines (AND set numNNServerThreadsPerModel above): +# trtDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0 +# trtDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1 +# trtDeviceToUseThread2 = 2 # change this if the third GPU you want to use turns out to be not device 2 + +# You can probably guess the pattern if you have four, five, etc. GPUs. + + +# CUDA GPU settings-------------------------------------- +# These only apply when using the CUDA version of KataGo. + +# IF USING ONE GPU: optionally uncomment and change this if the GPU you want to use turns out to be not device 0 +# cudaDeviceToUse = 0 + +# IF USING TWO GPUS: Uncomment these two lines (AND set numNNServerThreadsPerModel above): +# cudaDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0 +# cudaDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1 + +# IF USING THREE GPUS: Uncomment these three lines (AND set numNNServerThreadsPerModel above): +# cudaDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0 +# cudaDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1 +# cudaDeviceToUseThread2 = 2 # change this if the third GPU you want to use turns out to be not device 2 + +# You can probably guess the pattern if you have four, five, etc. GPUs. + +# KataGo will automatically use FP16 or not based on the compute capability of your NVIDIA GPU. If you +# want to try to force a particular behavior though you can uncomment these lines and change them +# to "true" or "false". E.g. it's using FP16 but on your card that's giving an error, or it's not using +# FP16 but you think it should. +# cudaUseFP16 = auto +# cudaUseNHWC = auto + + +# OpenCL GPU settings-------------------------------------- +# These only apply when using the OpenCL version of KataGo. + +# Uncomment to tune OpenCL for every board size separately, rather than only the largest possible size +# openclReTunePerBoardSize = true + +# IF USING ONE GPU: optionally uncomment and change this if the best device to use is guessed incorrectly. +# The default behavior tries to guess the 'best' GPU or device on your system to use, usually it will be a good guess. +# openclDeviceToUse = 0 + +# IF USING TWO GPUS: Uncomment these two lines and replace X and Y with the device ids of the devices you want to use. +# It might NOT be 0 and 1, some computers will have many OpenCL devices. You can see what the devices are when +# KataGo starts up - it should print or log all the devices it finds. +# (AND also set numNNServerThreadsPerModel above) +# openclDeviceToUseThread0 = X +# openclDeviceToUseThread1 = Y + +# IF USING THREE GPUS: Uncomment these three lines and replace X and Y and Z with the device ids of the devices you want to use. +# It might NOT be 0 and 1 and 2, some computers will have many OpenCL devices. You can see what the devices are when +# KataGo starts up - it should print or log all the devices it finds. +# (AND also set numNNServerThreadsPerModel above) +# openclDeviceToUseThread0 = X +# openclDeviceToUseThread1 = Y +# openclDeviceToUseThread2 = Z + +# You can probably guess the pattern if you have four, five, etc. GPUs. + +# KataGo will automatically use FP16 or not based on testing your GPU during tuning. If you +# want to try to force a particular behavior though you can uncomment this lines and change it +# to "true" or "false". This is a fairly blunt setting - more detailed settings are testable +# by rerunning the tuner with various arguments. +# openclUseFP16 = auto + + +# Eigen-specific settings-------------------------------------- +# These only apply when using the Eigen (pure CPU) version of KataGo. + +# This is the number of CPU threads for evaluating the neural net on the Eigen backend. +# It defaults to numSearchThreads. +# numEigenThreadsPerModel = X + + +# Root move selection and biases------------------------------------------------------------------------------ +# Uncomment and edit any of the below values to change them from their default. + +# If provided, force usage of a specific seed for various things in the search instead of randomizing +# searchRandSeed = hijklmn + +# Temperature for the early game, randomize between chosen moves with this temperature +# chosenMoveTemperatureEarly = 0.5 +# Decay temperature for the early game by 0.5 every this many moves, scaled with board size. +# chosenMoveTemperatureHalflife = 19 +# At the end of search after the early game, randomize between chosen moves with this temperature +# chosenMoveTemperature = 0.10 +# Subtract this many visits from each move prior to applying chosenMoveTemperature +# (unless all moves have too few visits) to downweight unlikely moves +# chosenMoveSubtract = 0 +# The same as chosenMoveSubtract but only prunes moves that fall below the threshold, does not affect moves above +# chosenMovePrune = 1 + +# Number of symmetries to sample (WITHOUT replacement) and average at the root +# rootNumSymmetriesToSample = 1 + +# Using LCB for move selection? +# useLcbForSelection = true +# How many stdevs a move needs to be better than another for LCB selection +# lcbStdevs = 5.0 +# Only use LCB override when a move has this proportion of visits as the top move +# minVisitPropForLCB = 0.15 + +# Internal params------------------------------------------------------------------------------ +# Uncomment and edit any of the below values to change them from their default. + +# Scales the utility of winning/losing +# winLossUtilityFactor = 1.0 +# Scales the utility for trying to maximize score +# staticScoreUtilityFactor = 0.10 +# dynamicScoreUtilityFactor = 0.30 +# Adjust dynamic score center this proportion of the way towards zero, capped at a reasonable amount. +# dynamicScoreCenterZeroWeight = 0.20 +# dynamicScoreCenterScale = 0.75 +# The utility of getting a "no result" due to triple ko or other long cycle in non-superko rulesets (-1 to 1) +# noResultUtilityForWhite = 0.0 +# The number of wins that a draw counts as, for white. (0 to 1) +# drawEquivalentWinsForWhite = 0.5 + +# Exploration constant for mcts +# cpuctExploration = 1.0 +# cpuctExplorationLog = 0.45 + +# Parameters that control exploring more in volatile positions, exploring less in stable positions. +# cpuctUtilityStdevPrior = 0.40 +# cpuctUtilityStdevPriorWeight = 2.0 +# cpuctUtilityStdevScale = 0.85 + +# FPU reduction constant for mcts +# fpuReductionMax = 0.2 +# rootFpuReductionMax = 0.1 +# fpuParentWeightByVisitedPolicy = true + +# Parameters that control weighting of evals based on the net's own self-reported uncertainty. +# useUncertainty = true +# uncertaintyExponent = 1.0 +# uncertaintyCoeff = 0.25 + +# Amount to apply a downweighting of children with very bad values relative to good ones +# valueWeightExponent = 0.25 + +# Slight incentive for the bot to behave human-like with regard to passing at the end, filling the dame, +# not wasting time playing in its own territory, etc, and not play moves that are equivalent in terms of +# points but a bit more unfriendly to humans. +# rootEndingBonusPoints = 0.5 + +# Make the bot prune useless moves that are just prolonging the game to avoid losing yet +# rootPruneUselessMoves = true + +# Apply bias correction based on local pattern keys +# subtreeValueBiasFactor = 0.45 +# subtreeValueBiasWeightExponent = 0.85 + +# Use graph search rather than tree search - identify and share search for transpositions. +# useGraphSearch = true + +# How much to shard the node table for search synchronization +# nodeTableShardsPowerOfTwo = 16 +# How many virtual losses to add when a thread descends through a node +# numVirtualLossesPerThread = 1 + +# Improve the quality of evals under heavy multithreading +# useNoisePruning = true + + +# Avoid SGF Patterns ------------------------------------------------------------------------------ +# The parameters in this section provide a powerful way to customize KataGo to avoid moves that follow specific patterns +# based on a set of provided SGF files loaded upon startup. Uncomment them to use this feature. +# Additionally, if the SGF file contains the string %SKIP% in a comment on a move, that move will be ignored for this purpose. + +# Load sgf files from this directory when the engine is started (ONLY on startup, will not reload unless engine is restarted) +# avoidSgfPatternDirs = path/to/directory/with/sgfs/ + +# Penalize this much utility per matching move. +# Set this negative if you instead want to make KataGo favor the SGF patterns instead of penalizing it! +# This number does not need to be large, even 0.001 will make a difference. Too-large values may lead to bad play. +# avoidSgfPatternUtility = 0.001 + +# Optional - load only the newest this many files +# avoidSgfPatternMaxFiles = 20 + +# Optional - Penalty is multiplied by this per each older SGF file, so that old sgf files matter less than newer ones. +# avoidSgfPatternLambda = 0.90 + +# Optional - pay attention only to moves that were made by players with this name. +# For example you can set it to the name that your bot's past games will show up as in the SGF, so that the bot will only avoid repeating +# moves that itself made in past games, not the moves that its opponents made. +# avoidSgfPatternAllowedNames = my-ogs-bot-name1,my-ogs-bot-name2 + +# Optional - Ignore any moves in SGF files that occurred before this turn number. +# avoidSgfPatternMinTurnNumber = 0 + +# For more avoid patterns: +# You can also specify a second set of parameters, and a third, fourth, etc by numbering 2,3,4,... +# avoidSgf2PatternDirs = ... +# avoidSgf2PatternUtility = ... +# avoidSgf2PatternMaxFiles = ... +# avoidSgf2PatternLambda = ... +# avoidSgf2PatternAllowedNames = ... +# avoidSgf2PatternMinTurnNumber = ... diff --git a/cpp/configs/gtp_tuned.cfg b/cpp/configs/gtp_tuned.cfg new file mode 100644 index 000000000..4003786f2 --- /dev/null +++ b/cpp/configs/gtp_tuned.cfg @@ -0,0 +1,468 @@ +# Config for KataGo C++ GTP engine, i.e. "./katago.exe gtp" + +# RUNNING ON AN ONLINE SERVER OR IN A REAL TOURNAMENT OR MATCH: +# If you plan to do so, you may want to read through the "Rules" section +# below carefully for proper handling of komi and handicap games and end-of-game cleanup +# and various other details. + +# NOTES ABOUT PERFORMANCE AND MEMORY USAGE: +# You will likely want to tune one or more the following: +# +# numSearchThreads: +# The number of CPU threads to use. If your GPU is powerful, it can actually be much higher than +# the number of cores on your processor because you will need many threads to feed large enough +# batches to make good use of the GPU. +# +# The "./katago benchmark" command can help you tune this parameter, as well as to test out the effect +# of changes to any of the other parameters below! +# +# nnCacheSizePowerOfTwo: +# This controls the NN Cache size, which is the primary RAM/memory use. +# Increase this if you don't mind the memory use and want better performance for searches with +# tens of thousands of visits or more. Decrease this if you want to limit memory usage. +# +# If you're someone who is happy to do a bit of math - each neural net entry takes very +# approximately 1.5KB, except when using whole-board ownership/territory visualizations, each +# entry will take very approximately 3KB. The number of entries is (2 ** nnCacheSizePowerOfTwo), +# for example 2 ** 18 = 262144. +# +# OTHER NOTES: +# If you have more than one GPU, take a look at "OpenCL GPU settings" or "CUDA GPU settings" below. +# +# If using OpenCL, you will want to verify that KataGo is picking up the correct device! +# (e.g. some systems may have both an Intel CPU OpenCL and GPU OpenCL, if KataGo appears to pick +# the wrong one, you correct this by specifying "openclGpuToUse" below). +# +# You may also want to adjust "maxVisits", "ponderingEnabled", "resignThreshold", and possibly +# other parameters depending on your intended usage. +# +# ---------------------------------------------------------------------------------------- + +# For the `katago gtp` command, ALL of THE BELOW VALUES MAY BE SET OR OVERRIDDEN if desired via +# the command line arguments: +# -override-config KEY=VALUE,KEY=VALUE,... + +# Logs and files-------------------------------------------------------------------------- + +# Where to output log? +logDir = gtp_logs # Each run of KataGo will log to a separate file in this dir +# logDirDated = gtp_logs # Use this instead of logDir to also write separate dated subdirs +# logFile = gtp.log # Use this instead of logDir to just specify a single file directly + +# Logging options +logAllGTPCommunication = true +logSearchInfo = true +logToStderr = false + +# KataGo will display some info to stderr on GTP startup +# Uncomment this to suppress that and remain silent +# startupPrintMessageToStderr = false + +# Chat some stuff to stderr, for use in things like malkovich chat to OGS. +# ogsChatToStderr = true + +# Optionally override where KataGo will attempt to save things like openCLTuner files and other cached data. +# homeDataDir = DIRECTORY + +# Analysis------------------------------------------------------------------------------------ + +# Configure the maximum length of analysis printed out by lz-analyze and other places. +# Controls the number of moves after the first move in a variation. +# analysisPVLen = 15 + +# Report winrates for chat and analysis as (BLACK|WHITE|SIDETOMOVE). +# Default is SIDETOMOVE, which is what tools that use LZ probably also expect +# reportAnalysisWinratesAs = SIDETOMOVE + +# Larger values will make KataGo explore the top move(s) less deeply and accurately, +# but explore and give evaluations to a greater variety of moves, for analysis (does NOT affect play). +# Defaults to 0.04. +# An extreme value like 1 will distribute many playouts across every move on the board, even very bad moves. +# analysisWideRootNoise = 0.04 + + +# Default rules------------------------------------------------------------------------------------ +# See https://lightvector.github.io/KataGo/rules.html for a description of the rules. +# These rules are defaults and can be changed mid-run by several custom GTP commands. +# See https://github.com/lightvector/KataGo/blob/master/docs/GTP_Extensions.md for those commands. + +# Some other legal values are: "chinese", "japanese", "korean", "aga", "chinese-ogs", "new-zealand". +# KataGo does not claim to exactly match any particular human ruleset, but KataGo will try to behave +# as closely as possible given the rules it has implemented. +rules = tromp-taylor + +# Use the below instead to specify an arbitrary combination of individual rules. + +# koRule = SIMPLE # Simple ko rules (triple ko = no result) +# koRule = POSITIONAL # Positional superko +# koRule = SITUATIONAL # Situational superko + +# scoringRule = AREA # Area scoring +# scoringRule = TERRITORY # Territory scoring (uses a sort of special computer-friendly territory ruleset) + +# taxRule = NONE # All surrounded empty points are scored +# taxRule = SEKI # Eyes in seki do NOT count as points +# taxRule = ALL # All groups are taxed up to 2 points for the two eyes needed to live + +# multiStoneSuicideLegal = true # Is multiple-stone suicide legal? (Single-stone suicide is always illegal). + +# hasButton = false # Set to true when area scoring to award 0.5 points to the first pass. + +# friendlyPassOk = true # Set to true except for computer rulesets that requires capturing all stones before passing. + +# whiteHandicapBonus = 0 # In handicap games, give white no compensation for black's handicap stones (Tromp-taylor, NZ, JP) +# whiteHandicapBonus = N-1 # In handicap games, give white N-1 points for black's handicap stones (AGA) +# whiteHandicapBonus = N # In handicap games, give white N points for black's handicap stones (Chinese) + +# Uncomment and change to adjust what board size KataGo uses upon startup by default if GTP doesn't specify. +# defaultBoardSize = 19 +# Specify this to force a particular komi, EVEN if the GUI or GTP controller tries to set a different one +# ignoreGTPAndForceKomi = 7 + +# Bot behavior--------------------------------------------------------------------------------------- + +# Resignation ------------- + +# Resignation occurs if for at least resignConsecTurns in a row, +# the winLossUtility (which is on a [-1,1] scale) is below resignThreshold. +allowResignation = true +resignThreshold = -0.90 +resignConsecTurns = 3 +# Uncomment to make katago not resign close games, behind by fewer than this many points +# resignMinScoreDifference = 10 + +# Handicap ------------- + +# Assume that if black makes many moves in a row right at the start of the game, then the game is a handicap game. +# This is necessary on some servers and for some GUIs and also when initializing from many SGF files, which may +# set up a handicap game using repeated GTP "play" commands for black rather than GTP "place_free_handicap" commands. +# However, it may also lead to incorrect understanding of komi if whiteHandicapBonus is used and a server does NOT +# have such a practice. +# Defaults to true! Uncomment and set to false to disable this behavior. +# assumeMultipleStartingBlackMovesAreHandicap = true + +# Makes katago dynamically adjust in handicap or altered-komi games to assume based on those game settings that it +# must be stronger or weaker than the opponent and to play accordingly. Greatly improves handicap +# strength by biasing winrates and scores to favor appropriate safe/aggressive play. +# Does NOT affect analysis (lz-analyze, kata-analyze, used by programs like Lizzie) so analysis remains unbiased. +# Uncomment and set this to 0 to disable this and make KataGo play the same always. +# dynamicPlayoutDoublingAdvantageCapPerOppLead = 0.045 + +# Instead of a dynamic level, you can uncomment this and set this to a value from -3.0 to 3.0 to set KataGo's aggression to a FIXED level. +# DOES affect analysis tools (lz-analyze, kata-analyze, used by programs like Lizzie). +# Negative makes KataGo behave as if it is much weaker than the opponent, preferring to play defensively. +# Positive makes KataGo behave as if it is much stronger than the opponent, prefering to play aggressively or even overplay slightly. +# If this and "dynamicPlayoutDoublingAdvantageCapPerOppLead" are BOTH set then dynamic will be used for all games and this fixed +# value will be used for analysis tools. +# playoutDoublingAdvantage = 0.0 + +# Uncommenting one of these will enforce that the FIXED playoutDoublingAdvantage will only apply when KataGo plays the specified color +# and will be negated when playing the opposite color. +# playoutDoublingAdvantagePla = BLACK +# playoutDoublingAdvantagePla = WHITE + +# Passing and cleanup ------------- + +# Make the bot never assume that its pass will end the game, even if passing would end and "win" under Tromp-Taylor rules. +# Usually this is a good idea when using it for analysis or playing on servers where scoring may be implemented non-tromp-taylorly. +# Defaults to true! Uncomment and set to false to disable this. +# conservativePass = true + +# When using territory scoring, self-play games continue beyond two passes with special cleanup +# rules that may be confusing for human players. This option prevents the special cleanup phases from being +# reachable when using the bot for GTP play. +# Defaults to true! Uncomment and set to false if you want KataGo to be able to enter special cleanup. +# For example, if you are testing it against itself, or against another bot that has precisely implemented the rules +# documented at https://lightvector.github.io/KataGo/rules.html +# preventCleanupPhase = true + +# Misc Behavior -------------------- + +# If the board is symmetric, search only one copy of each equivalent move. Attempts to also account for ko/superko, will not theoretically perfect for superko. +# Uncomment and set to false to disable this. +# rootSymmetryPruning = true + +# Uncomment and set to true to make KataGo avoid a particular joseki that some KataGo nets misevaluate, +# and also to improve opening diversity versus some particular other bots that like to play it all the time. +# avoidMYTDaggerHack = false + +# Have KataGo mildly prefer to avoid playing the same joseki in every corner of the board. +# Uncomment to set to a specific value. Otherwise, defaults to 0 in even games, and to 0.005 in handicap games. +# See also the Avoid SGF mechanism at the bottom of this config. +# avoidRepeatedPatternUtility = 0.0 + +# Experimental logic to make KataGo fight a bit against mirror Go even with unfavorable komi. +# Enabled by default for GTP play, disabled for GTP analysis (i.e lizzie) and analysis engine. +# Uncomment and set to true to enable it for analysis, or false to disable it fully. +# antiMirror = true + +# Search limits----------------------------------------------------------------------------------- + +# For all of "maxVisits", "maxPlayouts", "maxTime", search will still try to follow GTP time controls and may make a move +# faster than the specified max if GTP tells it that it is playing under a clock as well in the current game. + +# If provided, limit maximum number of root visits per search to this much. (With tree reuse, visits do count earlier search) +maxVisits = 500 +# If provided, limit maximum number of new playouts per search to this much. (With tree reuse, playouts do not count earlier search) +# maxPlayouts = 300 +# If provided, cap search time at this many seconds. +# maxTime = 10 + +# Ponder on the opponent's turn? +ponderingEnabled = false +maxTimePondering = 60 # Maximum time to ponder, in seconds. Comment out to make unlimited. +# Note: you can set "maxVisitsPondering" or "maxPlayoutsPondering" too. + +# Approx number of seconds to buffer for lag for GTP time controls - will move a bit faster assuming there is this much lag per move. +lagBuffer = 1.0 + +# Number of threads to use in search +numSearchThreads = 6 + +# Play a little faster if the opponent is passing, for friendliness +searchFactorAfterOnePass = 0.50 +searchFactorAfterTwoPass = 0.25 +# Play a little faster if super-winning, for friendliness +searchFactorWhenWinning = 0.40 +searchFactorWhenWinningThreshold = 0.95 + +# GPU Settings------------------------------------------------------------------------------- + +# Maximum number of positions to send to a single GPU at once. +# The default value here is roughly equal to numSearchThreads, but you can specify it manually +# if you are running out of memory, or if you are using multiple GPUs that expect to split +# up the work. +# nnMaxBatchSize = + +# Cache up to (2 ** this) many neural net evaluations in case of transpositions in the tree. +# Uncomment and edit to change if you want to adjust a major component of KataGo's RAM usage. +# nnCacheSizePowerOfTwo = 20 + +# Size of mutex pool for nnCache is (2 ** this). +# nnMutexPoolSizePowerOfTwo = 16 + +# Randomize board orientation when running neural net evals? Uncomment and set to false to disable. +# nnRandomize = true +# If provided, force usage of a specific seed for nnRandomize instead of randomizing. +# nnRandSeed = abcdefg + +# TO USE MULTIPLE GPUS: +# Set this to the number of GPUs you have and/or would like to use. +# **AND** if it is more than 1, uncomment the appropriate CUDA or OpenCL section below. +# numNNServerThreadsPerModel = 1 + + +# TENSORRT GPU settings-------------------------------------- +# These only apply when using the TENSORRT version of KataGo. + +# IF USING ONE GPU: optionally uncomment and change this if the GPU you want to use turns out to be not device 0 +# trtDeviceToUse = 0 + +# IF USING TWO GPUS: Uncomment these two lines (AND set numNNServerThreadsPerModel above): +# trtDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0 +# trtDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1 + +# IF USING THREE GPUS: Uncomment these three lines (AND set numNNServerThreadsPerModel above): +# trtDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0 +# trtDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1 +# trtDeviceToUseThread2 = 2 # change this if the third GPU you want to use turns out to be not device 2 + +# You can probably guess the pattern if you have four, five, etc. GPUs. + + +# CUDA GPU settings-------------------------------------- +# These only apply when using the CUDA version of KataGo. + +# IF USING ONE GPU: optionally uncomment and change this if the GPU you want to use turns out to be not device 0 +# cudaDeviceToUse = 0 + +# IF USING TWO GPUS: Uncomment these two lines (AND set numNNServerThreadsPerModel above): +# cudaDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0 +# cudaDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1 + +# IF USING THREE GPUS: Uncomment these three lines (AND set numNNServerThreadsPerModel above): +# cudaDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0 +# cudaDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1 +# cudaDeviceToUseThread2 = 2 # change this if the third GPU you want to use turns out to be not device 2 + +# You can probably guess the pattern if you have four, five, etc. GPUs. + +# KataGo will automatically use FP16 or not based on the compute capability of your NVIDIA GPU. If you +# want to try to force a particular behavior though you can uncomment these lines and change them +# to "true" or "false". E.g. it's using FP16 but on your card that's giving an error, or it's not using +# FP16 but you think it should. +# cudaUseFP16 = auto +# cudaUseNHWC = auto + + +# OpenCL GPU settings-------------------------------------- +# These only apply when using the OpenCL version of KataGo. + +# Uncomment to tune OpenCL for every board size separately, rather than only the largest possible size +# openclReTunePerBoardSize = true + +# IF USING ONE GPU: optionally uncomment and change this if the best device to use is guessed incorrectly. +# The default behavior tries to guess the 'best' GPU or device on your system to use, usually it will be a good guess. +# openclDeviceToUse = 0 + +# IF USING TWO GPUS: Uncomment these two lines and replace X and Y with the device ids of the devices you want to use. +# It might NOT be 0 and 1, some computers will have many OpenCL devices. You can see what the devices are when +# KataGo starts up - it should print or log all the devices it finds. +# (AND also set numNNServerThreadsPerModel above) +# openclDeviceToUseThread0 = X +# openclDeviceToUseThread1 = Y + +# IF USING THREE GPUS: Uncomment these three lines and replace X and Y and Z with the device ids of the devices you want to use. +# It might NOT be 0 and 1 and 2, some computers will have many OpenCL devices. You can see what the devices are when +# KataGo starts up - it should print or log all the devices it finds. +# (AND also set numNNServerThreadsPerModel above) +# openclDeviceToUseThread0 = X +# openclDeviceToUseThread1 = Y +# openclDeviceToUseThread2 = Z + +# You can probably guess the pattern if you have four, five, etc. GPUs. + +# KataGo will automatically use FP16 or not based on testing your GPU during tuning. If you +# want to try to force a particular behavior though you can uncomment this lines and change it +# to "true" or "false". This is a fairly blunt setting - more detailed settings are testable +# by rerunning the tuner with various arguments. +# openclUseFP16 = auto + + +# Eigen-specific settings-------------------------------------- +# These only apply when using the Eigen (pure CPU) version of KataGo. + +# This is the number of CPU threads for evaluating the neural net on the Eigen backend. +# It defaults to numSearchThreads. +# numEigenThreadsPerModel = X + + +# Root move selection and biases------------------------------------------------------------------------------ +# Uncomment and edit any of the below values to change them from their default. + +# If provided, force usage of a specific seed for various things in the search instead of randomizing +# searchRandSeed = hijklmn + +# Temperature for the early game, randomize between chosen moves with this temperature +# chosenMoveTemperatureEarly = 0.5 +# Decay temperature for the early game by 0.5 every this many moves, scaled with board size. +# chosenMoveTemperatureHalflife = 19 +# At the end of search after the early game, randomize between chosen moves with this temperature +# chosenMoveTemperature = 0.10 +# Subtract this many visits from each move prior to applying chosenMoveTemperature +# (unless all moves have too few visits) to downweight unlikely moves +# chosenMoveSubtract = 0 +# The same as chosenMoveSubtract but only prunes moves that fall below the threshold, does not affect moves above +# chosenMovePrune = 1 + +# Number of symmetries to sample (WITHOUT replacement) and average at the root +# rootNumSymmetriesToSample = 1 + +# Using LCB for move selection? +# useLcbForSelection = true +# How many stdevs a move needs to be better than another for LCB selection +# lcbStdevs = 5.0 +# Only use LCB override when a move has this proportion of visits as the top move +# minVisitPropForLCB = 0.15 + +# Internal params------------------------------------------------------------------------------ +# Uncomment and edit any of the below values to change them from their default. + +# Scales the utility of winning/losing +# winLossUtilityFactor = 1.0 +# Scales the utility for trying to maximize score +# staticScoreUtilityFactor = 0.10 +# dynamicScoreUtilityFactor = 0.30 +# Adjust dynamic score center this proportion of the way towards zero, capped at a reasonable amount. +# dynamicScoreCenterZeroWeight = 0.20 +# dynamicScoreCenterScale = 0.75 +# The utility of getting a "no result" due to triple ko or other long cycle in non-superko rulesets (-1 to 1) +# noResultUtilityForWhite = 0.0 +# The number of wins that a draw counts as, for white. (0 to 1) +# drawEquivalentWinsForWhite = 0.5 + +# Exploration constant for mcts +# cpuctExploration = 1.0 +# cpuctExplorationLog = 0.45 + +# Parameters that control exploring more in volatile positions, exploring less in stable positions. +# cpuctUtilityStdevPrior = 0.40 +# cpuctUtilityStdevPriorWeight = 2.0 +# cpuctUtilityStdevScale = 0.85 + +# FPU reduction constant for mcts +# fpuReductionMax = 0.2 +# rootFpuReductionMax = 0.1 +# fpuParentWeightByVisitedPolicy = true + +# Parameters that control weighting of evals based on the net's own self-reported uncertainty. +# useUncertainty = true +# uncertaintyExponent = 1.0 +# uncertaintyCoeff = 0.25 + +# Amount to apply a downweighting of children with very bad values relative to good ones +# valueWeightExponent = 0.25 + +# Slight incentive for the bot to behave human-like with regard to passing at the end, filling the dame, +# not wasting time playing in its own territory, etc, and not play moves that are equivalent in terms of +# points but a bit more unfriendly to humans. +# rootEndingBonusPoints = 0.5 + +# Make the bot prune useless moves that are just prolonging the game to avoid losing yet +# rootPruneUselessMoves = true + +# Apply bias correction based on local pattern keys +# subtreeValueBiasFactor = 0.45 +# subtreeValueBiasWeightExponent = 0.85 + +# Use graph search rather than tree search - identify and share search for transpositions. +# useGraphSearch = true + +# How much to shard the node table for search synchronization +# nodeTableShardsPowerOfTwo = 16 +# How many virtual losses to add when a thread descends through a node +# numVirtualLossesPerThread = 1 + +# Improve the quality of evals under heavy multithreading +# useNoisePruning = true + + +# Avoid SGF Patterns ------------------------------------------------------------------------------ +# The parameters in this section provide a powerful way to customize KataGo to avoid moves that follow specific patterns +# based on a set of provided SGF files loaded upon startup. Uncomment them to use this feature. +# Additionally, if the SGF file contains the string %SKIP% in a comment on a move, that move will be ignored for this purpose. + +# Load sgf files from this directory when the engine is started (ONLY on startup, will not reload unless engine is restarted) +# avoidSgfPatternDirs = path/to/directory/with/sgfs/ + +# Penalize this much utility per matching move. +# Set this negative if you instead want to make KataGo favor the SGF patterns instead of penalizing it! +# This number does not need to be large, even 0.001 will make a difference. Too-large values may lead to bad play. +# avoidSgfPatternUtility = 0.001 + +# Optional - load only the newest this many files +# avoidSgfPatternMaxFiles = 20 + +# Optional - Penalty is multiplied by this per each older SGF file, so that old sgf files matter less than newer ones. +# avoidSgfPatternLambda = 0.90 + +# Optional - pay attention only to moves that were made by players with this name. +# For example you can set it to the name that your bot's past games will show up as in the SGF, so that the bot will only avoid repeating +# moves that itself made in past games, not the moves that its opponents made. +# avoidSgfPatternAllowedNames = my-ogs-bot-name1,my-ogs-bot-name2 + +# Optional - Ignore any moves in SGF files that occurred before this turn number. +# avoidSgfPatternMinTurnNumber = 0 + +# For more avoid patterns: +# You can also specify a second set of parameters, and a third, fourth, etc by numbering 2,3,4,... +# avoidSgf2PatternDirs = ... +# avoidSgf2PatternUtility = ... +# avoidSgf2PatternMaxFiles = ... +# avoidSgf2PatternLambda = ... +# avoidSgf2PatternAllowedNames = ... +# avoidSgf2PatternMinTurnNumber = ... + + + + diff --git a/cpp/configs/match_example.cfg b/cpp/configs/match_example.cfg index 3c3e9513b..c65eca84a 100644 --- a/cpp/configs/match_example.cfg +++ b/cpp/configs/match_example.cfg @@ -43,16 +43,16 @@ logSearchInfo = false logMoves = false -logGamesEvery = 50 +logGamesEvery = 10 logToStdout = true # Bots------------------------------------------------------------------------------------- # For multiple bots, you can specify their names as botName0,botName1, etc. # If the bots are using different models, specify nnModelFile0, nnModelFile1, etc. -numBots=1 -botName=FOO -nnModelFile=PATH_TO_MODEL +# numBots=1 +# botName=FOO +# nnModelFile=PATH_TO_MODEL # These bots will not play each other, but will still be opponents for other bots # secondaryBots = 0,1,3 @@ -60,31 +60,201 @@ nnModelFile=PATH_TO_MODEL # bots defined but you want to only selectively enable a few. # includeBots = 0,2,6 +numBots = 83 +botName0 = dummy +botName1 = daoqi-s583680-d41745 +botName2 = daoqi-s437376-d86279 +botName3 = TODO +botName4 = TODO +botName5 = TODO +botName6 = TODO +botName7 = TODO +botName8 = TODO +botName9 = TODO +botName10 = TODO +botName11 = TODO +botName12 = TODO +botName13 = TODO +botName14 = TODO +botName15 = TODO +botName16 = TODO +botName17 = TODO +botName18 = TODO +botName19 = TODO +botName20 = TODO +botName21 = TODO +botName22 = TODO +botName23 = TODO +botName24 = TODO +botName25 = TODO +botName26 = TODO +botName27 = TODO +botName28 = TODO +botName29 = TODO +botName30 = TODO +botName31 = TODO +botName32 = TODO +botName33 = TODO +botName34 = TODO +botName35 = TODO +botName36 = TODO +botName37 = TODO +botName38 = TODO +botName39 = TODO +botName40 = TODO +botName41 = TODO +botName42 = TODO +botName43 = TODO +botName44 = TODO +botName45 = TODO +botName46 = TODO +botName47 = TODO +botName48 = TODO +botName49 = TODO +botName50 = TODO +botName51 = TODO +botName52 = TODO +botName53 = TODO +botName54 = TODO +botName55 = TODO +botName56 = daoqi-s27462400-d7834755 +botName57 = daoqi-s28096256-d8065839 +botName58 = daoqi-s28731264-d8293612 +botName59 = daoqi-s29096448-d8520277 +botName60 = daoqi-s17324544-d9313956 +botName61 = daoqi-s35788160-d9579671 +botName62 = daoqi-s36064384-d9694347 +botName63 = daoqi-s36335872-d9808736 +botName64 = daoqi-s36889088-d10038372 +botName65 = daoqi-s37169152-d10156350 +botName66 = daoqi-s38882688-d10899161 +botName67 = daoqi-s39148800-d11051308 +botName68 = daoqi-s39415040-d11203900 +botName69 = daoqi-s40080640-d11542882 +botName70 = daoqi-s40413184-d11733792 +botName71 = daoqi-s40746624-d11963425 +botName72 = daoqi-s21172736-d12529899 +botName73 = daoqi-s22906496-d13208116 +botName74 = daoqi-s23172608-d13319785 +botName75 = daoqi-s23439424-d13434081 +botName76 = daoqi-new-s43068032-d5768687 +botName77 = TODO +botName78 = TODO +botName79 = TODO +botName80 = TODO +botName81 = xu-100 +botName82 = xu-128 + +nnModelFile0 = /home/gcao/daoqi-opencl/models/dummy/model.bin.gz +nnModelFile1 = /home/gcao/daoqi-opencl/models/daoqi-s583680-d41745/model.bin.gz +nnModelFile2 = /home/gcao/daoqi-opencl/models/daoqi-s437376-d86279/model.bin.gz +nnModelFile3 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile4 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile5 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile6 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile7 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile8 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile9 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile10 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile11 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile12 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile13 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile14 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile15 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile16 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile17 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile18 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile19 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile20 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile21 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile22 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile23 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile24 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile25 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile26 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile27 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile28 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile29 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile30 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile31 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile32 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile33 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile34 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile35 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile36 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile37 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile38 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile39 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile40 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile41 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile42 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile43 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile44 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile45 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile46 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile47 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile48 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile49 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile50 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile51 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile52 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile53 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile54 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile55 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile56 = /home/gcao/daoqi-opencl/models/daoqi-s27462400-d7834755/model.bin.gz +nnModelFile57 = /home/gcao/daoqi-opencl/models/daoqi-s28096256-d8065839/model.bin.gz +nnModelFile58 = /home/gcao/daoqi-opencl/models/daoqi-s28731264-d8293612/model.bin.gz +nnModelFile59 = /home/gcao/daoqi-opencl/models/daoqi-s29096448-d8520277/model.bin.gz +nnModelFile60 = /home/gcao/daoqi-opencl/models/daoqi-s17324544-d9313956/model.bin.gz +nnModelFile61 = /home/gcao/daoqi-opencl/models/daoqi-s35788160-d9579671/model.bin.gz +nnModelFile62 = /home/gcao/daoqi-opencl/models/daoqi-s36064384-d9694347/model.bin.gz +nnModelFile63 = /home/gcao/daoqi-opencl/models/daoqi-s36335872-d9808736/model.bin.gz +nnModelFile64 = /home/gcao/daoqi-opencl/models/daoqi-s36889088-d10038372/model.bin.gz +nnModelFile65 = /home/gcao/daoqi-opencl/models/daoqi-s37169152-d10156350/model.bin.gz +nnModelFile66 = /home/gcao/daoqi-opencl/models/daoqi-s38882688-d10899161/model.bin.gz +nnModelFile67 = /home/gcao/daoqi-opencl/models/daoqi-s39148800-d11051308/model.bin.gz +nnModelFile68 = /home/gcao/daoqi-opencl/models/daoqi-s39415040-d11203900/model.bin.gz +nnModelFile69 = /home/gcao/daoqi-opencl/models/daoqi-s40080640-d11542882/model.bin.gz +nnModelFile70 = /home/gcao/daoqi-opencl/models/daoqi-s40413184-d11733792/model.bin.gz +nnModelFile71 = /home/gcao/daoqi-opencl/models/daoqi-s40746624-d11963425/model.bin.gz +nnModelFile72 = /home/gcao/daoqi-opencl/models/daoqi-s21172736-d12529899/model.bin.gz +nnModelFile73 = /home/gcao/daoqi-opencl/models/daoqi-s22906496-d13208116/model.bin.gz +nnModelFile74 = /home/gcao/daoqi-opencl/models/daoqi-s23172608-d13319785/model.bin.gz +nnModelFile75 = /home/gcao/daoqi-opencl/models/daoqi-s23439424-d13434081/model.bin.gz +nnModelFile76 = /home/gcao/daoqi-opencl/models/daoqi-new-s43068032-d5768687/model.bin.gz +nnModelFile77 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile78 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile79 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile80 = /home/gcao/daoqi-opencl/models/TODO/model.bin.gz +nnModelFile81 = /home/gcao/daoqi-opencl/models/daoqi-xu-100/model.bin.gz +nnModelFile82 = /home/gcao/daoqi-opencl/models/daoqi-xu-128/model.bin.gz + +includeBots = 76,82 # Match----------------------------------------------------------------------------------- -numGameThreads=8 # How many games to run in parallel at a time? +numGameThreads=100 # How many games to run in parallel at a time? -numGamesTotal=1000000 +numGamesTotal=10 maxMovesPerGame=1200 allowResignation = true resignThreshold = -0.95 -resignConsecTurns = 6 +resignConsecTurns = 3 # Rules------------------------------------------------------------------------------------ # See https://lightvector.github.io/KataGo/rules.html for a description of the rules. -koRules = SIMPLE,POSITIONAL,SITUATIONAL -scoringRules = AREA,TERRITORY -taxRules = NONE,SEKI,ALL -multiStoneSuicideLegals = false,true -hasButtons = false,true +koRules = POSITIONAL +scoringRules = AREA +taxRules = NONE +multiStoneSuicideLegals = false +hasButtons = false -bSizes = 19,13,9 -bSizeRelProbs = 90,5,5 +bSizes = 19 +bSizeRelProbs = 100 -komiAuto = True # Automatically adjust komi to what the neural nets think are fair -# komiMean = 7.5 # Specify explicit komi +# komiAuto = True # Automatically adjust komi to what the neural nets think are fair +komiMean = 7.5 # Specify explicit komi # policyInitAreaProp = 0 # compensateAfterPolicyInitProb = 1.0 # Additionally make komi fair this often after the high-temperature moves. # policyInitAreaTemperature = 1 @@ -94,17 +264,18 @@ handicapCompensateKomiProb = 1.0 # Search limits----------------------------------------------------------------------------------- -maxVisits = 500 +maxVisits = 600 # maxPlayouts = 300 # maxTime = 60 -numSearchThreads = 1 +numSearchThreads = 6 # GPU Settings------------------------------------------------------------------------------- -nnMaxBatchSize = 32 +# nnMaxBatchSize = 128 +nnMaxBatchSize = 6 nnCacheSizePowerOfTwo = 21 -nnMutexPoolSizePowerOfTwo = 17 +nnMutexPoolSizePowerOfTwo = 16 nnRandomize = true @@ -113,7 +284,7 @@ nnRandomize = true # that specify which threads should use which GPUs. # NOTE: This parameter is probably ONLY useful if you have multiple GPUs, since each GPU will need a thread. # If you're tuning single-GPU performance, use numSearchThreads instead. -numNNServerThreadsPerModel = 1 +# numNNServerThreadsPerModel = 1 # TENSORRT GPU settings-------------------------------------- @@ -176,9 +347,9 @@ numNNServerThreadsPerModel = 1 # Uncomment and edit any of the below values to change them from their default. # Values in this section can be specified per-bot as well -chosenMoveTemperatureEarly = 0.60 +# chosenMoveTemperatureEarly = 0.50 # chosenMoveTemperatureHalflife = 19 -chosenMoveTemperature = 0.20 +# chosenMoveTemperature = 0.10 # chosenMoveSubtract = 0 # chosenMovePrune = 1 @@ -200,8 +371,8 @@ chosenMoveTemperature = 0.20 # noResultUtilityForWhite = 0.0 # drawEquivalentWinsForWhite = 0.5 -# cpuctExploration = 0.9 -# cpuctExplorationLog = 0.4 +# cpuctExploration = 1.0 +# cpuctExplorationLog = 0.45 # fpuReductionMax = 0.2 # rootFpuReductionMax = 0.1 # fpuParentWeightByVisitedPolicy = true diff --git a/cpp/configs/match_example2.cfg b/cpp/configs/match_example2.cfg new file mode 100644 index 000000000..c31ca02a9 --- /dev/null +++ b/cpp/configs/match_example2.cfg @@ -0,0 +1,230 @@ +# Example config for C++ match runner +# This is an example template config for the "match" subcommand of KataGo. e.g: +# ./katago match -config-file configs/match_example.cfg -log-file match.log -sgf-output-dir match_sgfs/ +# +# On a good GPU, the match subcommand enables testing of KataGo nets against each other +# or a net against itself with different parameters vastly faster than anything +# else, because multiple games can share GPU batching. +# +# Beware however of using this to test differing numbers of threads or test time-based search limits. +# Because many games will be run simultaneously, they will compete from each other for compute power, +# and although the total will run much faster than if you had run them one by one, you might also get +# substantially different results not reflective of the strength of a configuration when run in a real +# match setting, on a machine by itself. For fixed numbers of visits or playouts instead of fixed time, +# and with numSearchThreads = 1, there should be no problem though, because then the compute time has +# no influence on the result of the computation. +# +# See gtp config for descriptions of most of these params. +# +# For almost any parameter in this config that is related to a bot, rather than to the match as a whole +# (so, visits, search parameters, model files, etc. but NOT the rules, max games, log info, etc) +# you can specify them differentially between different bots by appending the index of the bot. +# For example, if you were testing different numbers of visits, you could try: +# +# numBots = 3 +# botName0 = lowVisits +# botName1 = midVisits +# botName2 = highVisits +# +# maxVisits0 = 100 +# maxVisits1 = 300 +# maxVisits2 = 1000 +# +# Or, if you were testing different neural nets, with different search configurations, you could do: +# +# nnModelFile0 = path/to/first/model/file.bin.gz +# nnModelFile1 = path/to/second/model/file.bin.gz +# +# And specify different search parameters for them if you wanted: +# cpuctExploration0 = 1.5 +# cpuctExploration1 = 1.3 + +# Logs------------------------------------------------------------------------------------ + +logSearchInfo = false +logMoves = false +logGamesEvery = 10 +logToStdout = true + +# Bots------------------------------------------------------------------------------------- +# For multiple bots, you can specify their names as botName0,botName1, etc. +# If the bots are using different models, specify nnModelFile0, nnModelFile1, etc. + +# numBots=1 +# botName=FOO +# nnModelFile=PATH_TO_MODEL + +# These bots will not play each other, but will still be opponents for other bots +# secondaryBots = 0,1,3 +# Only these bots will actually play games. Useful if you have a config file with many more +# bots defined but you want to only selectively enable a few. +# includeBots = 0,2,6 + +numBots = 2 +botName0 = daoqi-new-s46734592-d6262713 +botName1 = daoqi-s23439424-d13434081 +# botName1 = xu-128 + +nnModelFile0 = /home/gcao/daoqi-opencl/models/daoqi-new-s46734592-d6262713/model.bin.gz +nnModelFile1 = /home/gcao/daoqi-opencl/models/daoqi-s23439424-d13434081/model.bin.gz +# nnModelFile1 = /home/gcao/daoqi-opencl/models/daoqi-xu-128/model.bin.gz + +includeBots = 0,1 + +# Match----------------------------------------------------------------------------------- +numGameThreads=100 # How many games to run in parallel at a time? + +numGamesTotal=10 +maxMovesPerGame=1200 + +allowResignation = true +resignThreshold = -0.95 +resignConsecTurns = 3 + +# Rules------------------------------------------------------------------------------------ +# See https://lightvector.github.io/KataGo/rules.html for a description of the rules. + +koRules = POSITIONAL +scoringRules = AREA +taxRules = NONE +multiStoneSuicideLegals = false +hasButtons = false + +bSizes = 19 +bSizeRelProbs = 100 + +# komiAuto = True # Automatically adjust komi to what the neural nets think are fair +komiMean = 7.5 # Specify explicit komi +# policyInitAreaProp = 0 +# compensateAfterPolicyInitProb = 1.0 # Additionally make komi fair this often after the high-temperature moves. +# policyInitAreaTemperature = 1 +handicapProb = 0.0 +handicapCompensateKomiProb = 1.0 +# numExtraBlackFixed = 3 # When playing handicap games, always use exactly this many extra black moves + +# Search limits----------------------------------------------------------------------------------- + +maxVisits = 600 +# maxVisits0 = 2000 +# maxVisits1 = 5000 +# maxPlayouts = 300 +# maxTime = 60 +# maxTime0 = 150 +# maxTime1 = 150 + +numSearchThreads = 6 + +# GPU Settings------------------------------------------------------------------------------- + +nnMaxBatchSize = 128 +nnCacheSizePowerOfTwo = 21 +nnMutexPoolSizePowerOfTwo = 16 +nnRandomize = true + + +# How many threads should there be to feed positions to the neural net? +# Server threads are indexed 0,1,...(n-1) for the purposes of the below GPU settings arguments +# that specify which threads should use which GPUs. +# NOTE: This parameter is probably ONLY useful if you have multiple GPUs, since each GPU will need a thread. +# If you're tuning single-GPU performance, use numSearchThreads instead. +# numNNServerThreadsPerModel = 1 + + +# TENSORRT GPU settings-------------------------------------- +# These only apply when using the TENSORRT version of KataGo. + +# IF USING ONE GPU: optionally uncomment and change this if the GPU you want to use turns out to be not device 0 +# trtDeviceToUse = 0 + +# IF USING TWO GPUS: Uncomment these two lines (AND set numNNServerThreadsPerModel above): +# trtDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0 +# trtDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1 + +# IF USING THREE GPUS: Uncomment these three lines (AND set numNNServerThreadsPerModel above): +# trtDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0 +# trtDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1 +# trtDeviceToUseThread2 = 2 # change this if the third GPU you want to use turns out to be not device 2 + +# You can probably guess the pattern if you have four, five, etc. GPUs. + + +# CUDA GPU settings-------------------------------------- +# For the below, "model" refers to a neural net, from the nnModelFile parameter(s) above. +# cudaGpuToUse = 0 #use gpu 0 for all server threads (numNNServerThreadsPerModel) unless otherwise specified per-model or per-thread-per-model +# cudaGpuToUseModel0 = 3 #use gpu 3 for model 0 for all threads unless otherwise specified per-thread for this model +# cudaGpuToUseModel1 = 2 #use gpu 2 for model 1 for all threads unless otherwise specified per-thread for this model +# cudaGpuToUseModel0Thread0 = 3 #use gpu 3 for model 0, server thread 0 +# cudaGpuToUseModel0Thread1 = 2 #use gpu 2 for model 0, server thread 1 + +# cudaUseFP16 = auto +# cudaUseNHWC = auto + + +# OpenCL GPU settings-------------------------------------- +# These only apply when using OpenCL as the backend for inference. +# (For GTP, we only ever have one model, when playing matches, we might have more than one, see match_example.cfg) + +# Default behavior is just to always use gpu 0, you will want to uncomment and adjust one or more of these lines +# to take advantage of a multi-gpu machine +# openclGpuToUse = 0 #use gpu 0 for all server threads (numNNServerThreadsPerModel) unless otherwise specified per-model or per-thread-per-model +# openclGpuToUseModel0 = 3 #use gpu 3 for model 0 for all threads unless otherwise specified per-thread for this model +# openclGpuToUseModel1 = 2 #use gpu 2 for model 1 for all threads unless otherwise specified per-thread for this model +# openclGpuToUseModel0Thread0 = 3 #use gpu 3 for model 0, server thread 0 +# openclGpuToUseModel0Thread1 = 2 #use gpu 2 for model 0, server thread 1 + +# Uncomment to tune OpenCL for every board size separately, rather than only the largest possible size +# openclReTunePerBoardSize = true + +# openclUseFP16 = auto + + +# Eigen-specific settings-------------------------------------- +# These only apply when using the Eigen (pure CPU) version of KataGo. + +# This is the number of CPU threads for evaluating the neural net on the Eigen backend. +# It defaults to numSearchThreads. +# numEigenThreadsPerModel = X + + +# Root move selection and biases------------------------------------------------------------------------------ +# Uncomment and edit any of the below values to change them from their default. +# Values in this section can be specified per-bot as well + +# chosenMoveTemperatureEarly = 0.50 +# chosenMoveTemperatureHalflife = 19 +# chosenMoveTemperature = 0.20 +# chosenMoveSubtract = 0 +# chosenMovePrune = 1 + +# rootNumSymmetriesToSample = 1 + +# useLcbForSelection = true +# lcbStdevs = 5.0 +# minVisitPropForLCB = 0.15 + +# Internal params------------------------------------------------------------------------------ +# Uncomment and edit any of the below values to change them from their default. +# Values in this section can be specified per-bot as well + +# winLossUtilityFactor = 1.0 +# staticScoreUtilityFactor = 0.00 +# dynamicScoreUtilityFactor = 0.25 +# dynamicScoreCenterZeroWeight = 0.25 +# dynamicScoreCenterScale = 0.50 +# noResultUtilityForWhite = 0.0 +# drawEquivalentWinsForWhite = 0.5 + +# cpuctExploration = 1.1 +# cpuctExplorationLog = 0.0 +# fpuReductionMax = 0.2 +# rootFpuReductionMax = 0.1 +# # fpuParentWeightByVisitedPolicy = true +# # valueWeightExponent = 0.25 +# rootEndingBonusPoints = 0.5 +# rootPruneUselessMoves = true +# # subtreeValueBiasFactor = 0.45 +# # subtreeValueBiasWeightExponent = 0.85 +# # useGraphSearch = true + +# # nodeTableShardsPowerOfTwo = 16 +# numVirtualLossesPerThread = 1 diff --git a/cpp/configs/selfplay1.cfg b/cpp/configs/selfplay1.cfg new file mode 100644 index 000000000..fea1971fd --- /dev/null +++ b/cpp/configs/selfplay1.cfg @@ -0,0 +1,174 @@ + +# Logs------------------------------------------------------------------------------------ + +logSearchInfo = false +logMoves = false +logGamesEvery = 10 +logToStdout = true + +# Data writing----------------------------------------------------------------------------------- + +dataBoardLen = 19 +maxDataQueueSize = 2000 +maxRowsPerTrainFile = 25000 +maxRowsPerValFile = 5000 +firstFileRandMinProp = 0.15 + +validationProp = 0.00 + +# Fancy game selfplay settings-------------------------------------------------------------------- + +# These take dominance - if a game is forked for any of these reasons, that will be the next game played. +# For forked games, randomization of rules and "initGamesWithPolicy" is disabled, komi is made fair with prob "forkCompensateKomiProb". +earlyForkGameProb = 0.04 # Fork to try alternative opening variety with this probability +earlyForkGameExpectedMoveProp = 0.025 # Fork roughly within the first (boardArea * this) many moves +forkGameProb = 0.01 # Fork to try alternative crazy move anywhere in game with this probability if not early forking +forkGameMinChoices = 3 # Choose from the best of at least this many random choices +earlyForkGameMaxChoices = 12 # Choose from the best of at most this many random choices +forkGameMaxChoices = 36 # Choose from the best of at most this many random choices +# Otherwise, with this probability, learn a bit more about the differing evaluation of seki in different rulesets. +sekiForkHackProb = 0.02 + +# Otherwise, play some proportion of games starting from SGF positions, with randomized rules (ignoring the sgf rules) +# On SGF positions, high temperature policy init is allowed +# startPosesProb = 0.0 # Play this proportion of games starting from SGF positions +# startPosesFromSgfDir = DIRECTORYPATH # Load SGFs from this dir +# startPosesLoadProb = 1.0 # Only load each position from each SGF with this chance (save memory) +# startPosesTurnWeightLambda = 0 # 0 = equal weight 0.01 = decrease probability by 1% per turn -0.01 = increase probability by 1% per turn. +# startPosesPolicyInitAreaProp = 0.0 # Same as policyInitAreaProp but for SGF positions + +# Otherwise, play some proportion of games starting from hint positions (generated using "dataminesgfs" command), with randomized rules. +# On hint positions, "initGamesWithPolicy" does not apply. +# hintPosesProb = 0.0 +# hintPosesDir = DIRECTORYPATH + +# Otherwise we are playing a "normal" game, potentially with handicap stones, depending on "handicapProb", and +# potentially with komi randomization, depending on things like "komiStdev", and potentially with different +# board sizes, etc. + +# Most of the remaining parameters here below apply regardless of the initialization, although a few of them +# vary depending on handicap vs normal game, and some are explicitly disabled (e.g. initGamesWithPolicy on hint positions). + +initGamesWithPolicy = true # Play the first few moves of a game high-temperaturely from policy +policyInitAreaProp = 0.04 # The avg number of moves to play +compensateAfterPolicyInitProb = 0.2 # Additionally make komi fair this often after the high-temperature moves. +sidePositionProb = 0.020 # With this probability, train on refuting bad alternative moves. + +cheapSearchProb = 0.75 # Do cheap searches with this probability +cheapSearchVisits = 100 # Number of visits for cheap search +cheapSearchTargetWeight = 0.0 # Training weight for cheap search + +reduceVisits = true # Reduce visits when one side is winning +reduceVisitsThreshold = 0.95 # How winning a side needs to be (winrate) +reduceVisitsThresholdLookback = 5 # How many consecutive turns needed to be that winning +reducedVisitsMin = 100 # Minimum number of visits (never reduce below this) +reducedVisitsWeight = 0.1 # Minimum training weight + +handicapAsymmetricPlayoutProb = 0.5 # In handicap games, play with unbalanced players with this probablity +normalAsymmetricPlayoutProb = 0.01 # In regular games, play with unbalanced players with this probability +maxAsymmetricRatio = 8.0 # Max ratio to unbalance visits between players +minAsymmetricCompensateKomiProb = 0.4 # Compensate komi with at least this probability for unbalanced players + +policySurpriseDataWeight = 0.5 # This proportion of training weight should be concentrated on surprising moves +valueSurpriseDataWeight = 0.1 # This proportion of training weight should be concentrated on surprising position results + +estimateLeadProb = 0.05 # Train lead, rather than just scoremean. Consumes a decent number of extra visits, can be quite slow using low visits to set too high. +switchNetsMidGame = true # When a new neural net is loaded, switch to it immediately instead of waiting for new game +fancyKomiVarying = true # In non-compensated handicap and fork games, vary komi to better learn komi and large score differences that would never happen in even games. + +# Match----------------------------------------------------------------------------------- + +numGamesPerCycle = 1500 +numGameThreads = 100 +maxMovesPerGame = 1600 + +# Rules------------------------------------------------------------------------------------ + +koRules = POSITIONAL +scoringRules = AREA,TERRITORY +taxRules = NONE +multiStoneSuicideLegals = false +hasButtons = false + +bSizes = 19 +bSizeRelProbs = 100 +allowRectangleProb = 0.00 # Play game on rectangular board with this probability + +komiAuto = True # Automatically adjust komi to what the neural nets think are fair based on the empty board, but still apply komiStdev. +# komiMean = 7.0 # Specify explicit komi +komiStdev = 1.0 # Standard deviation of random variation to komi. +komiBigStdevProb = 0.06 # Probability of applying komiBigStdev +komiBigStdev = 12.0 # Standard deviation of random big variation to komi + +handicapProb = 0.10 # Probability of handicap game +handicapCompensateKomiProb = 0.50 # In handicap games, adjust komi to fair with this probability based on the handicap placement +forkCompensateKomiProb = 0.80 # For forks, adjust komi to fair with this probability based on the forked position +sgfCompensateKomiProb = 0.90 # For sgfs, adjust komi to fair with this probability based on the specific starting position + +drawRandRadius = 0.5 +noResultStdev = 0.166666666 + +# Search limits----------------------------------------------------------------------------------- + +maxVisits = 600 +numSearchThreads = 6 + +# GPU Settings------------------------------------------------------------------------------- + +nnMaxBatchSize = 128 +nnCacheSizePowerOfTwo = 21 +nnMutexPoolSizePowerOfTwo = 15 +numNNServerThreadsPerModel = 1 +nnRandomize = true + +# CUDA GPU settings-------------------------------------- +# cudaDeviceToUse = 0 #use device 0 for all server threads (numNNServerThreadsPerModel) unless otherwise specified per-model or per-thread-per-model +# cudaDeviceToUseModel0 = 3 #use device 3 for model 0 for all threads unless otherwise specified per-thread for this model +# cudaDeviceToUseModel1 = 2 #use device 2 for model 1 for all threads unless otherwise specified per-thread for this model +# cudaDeviceToUseModel0Thread0 = 3 #use device 3 for model 0, server thread 0 +# cudaDeviceToUseModel0Thread1 = 2 #use device 2 for model 0, server thread 1 + +cudaUseFP16 = auto +cudaUseNHWC = auto + +# Root move selection and biases------------------------------------------------------------------------------ + +chosenMoveTemperatureEarly = 0.75 +chosenMoveTemperatureHalflife = 19 +chosenMoveTemperature = 0.15 +chosenMoveSubtract = 0 +chosenMovePrune = 1 + +rootNoiseEnabled = true +rootDirichletNoiseTotalConcentration = 10.83 +rootDirichletNoiseWeight = 0.25 + +rootDesiredPerChildVisitsCoeff = 2 +rootNumSymmetriesToSample = 4 + +useLcbForSelection = true +lcbStdevs = 5.0 +minVisitPropForLCB = 0.15 + +# Internal params------------------------------------------------------------------------------ + +winLossUtilityFactor = 1.0 +staticScoreUtilityFactor = 0.00 +dynamicScoreUtilityFactor = 0.40 +dynamicScoreCenterZeroWeight = 0.25 +dynamicScoreCenterScale = 0.50 +noResultUtilityForWhite = 0.0 +drawEquivalentWinsForWhite = 0.5 + +rootEndingBonusPoints = 0.5 +rootPruneUselessMoves = true + +rootPolicyTemperatureEarly = 1.25 +rootPolicyTemperature = 1.1 + +cpuctExploration = 1.1 +cpuctExplorationLog = 0.0 +fpuReductionMax = 0.2 +rootFpuReductionMax = 0.0 + +numVirtualLossesPerThread = 1 diff --git a/cpp/configs/selfplay2.cfg b/cpp/configs/selfplay2.cfg new file mode 100644 index 000000000..bbda0a7c0 --- /dev/null +++ b/cpp/configs/selfplay2.cfg @@ -0,0 +1,158 @@ + +# Logs------------------------------------------------------------------------------------ + +logSearchInfo = false +logMoves = false +logGamesEvery = 20 +logToStdout = true + +# Data writing----------------------------------------------------------------------------------- + +dataBoardLen = 19 +maxDataQueueSize = 2000 +maxRowsPerTrainFile = 25000 +maxRowsPerValFile = 5000 +firstFileRandMinProp = 0.15 + +validationProp = 0.00 + +# Fancy game selfplay settings-------------------------------------------------------------------- +initGamesWithPolicy = true +policyInitAreaProp = 0.04 +compensateAfterPolicyInitProb = 0.2 +forkSidePositionProb = 0.020 + +earlyForkGameProb = 0.05 +earlyForkGameExpectedMoveProp = 0.025 +forkGameProb = 0.06 +forkGameMinChoices = 3 +earlyForkGameMaxChoices = 12 +forkGameMaxChoices = 36 + +cheapSearchProb = 0.75 +cheapSearchVisits = 100 +cheapSearchTargetWeight = 0.0 + +reduceVisits = true +reduceVisitsThreshold = 0.9 +reduceVisitsThresholdLookback = 3 +reducedVisitsMin = 100 +reducedVisitsWeight = 0.1 + +handicapAsymmetricPlayoutProb = 0.5 +normalAsymmetricPlayoutProb = 0.01 +maxAsymmetricRatio = 8.0 +minAsymmetricCompensateKomiProb = 0.4 + +# startPosesProb = 0.0 +# startPosesFromSgfDir = DIRECTORYPATH +# startPosesLoadProb = 1.0 +# startPosesTurnWeightLambda = 0 +# startPosesPolicyInitAreaProp = 0.0 + +policySurpriseDataWeight = 0.5 +valueSurpriseDataWeight = 0.1 + +estimateLeadProb = 0.05 +switchNetsMidGame = true +fancyKomiVarying = true + +# Match----------------------------------------------------------------------------------- + +numGameThreads = 320 +maxMovesPerGame = 1600 + +# Rules------------------------------------------------------------------------------------ + +koRules = SIMPLE,POSITIONAL,SITUATIONAL +scoringRules = AREA,TERRITORY +taxRules = NONE,NONE,SEKI,SEKI,ALL +multiStoneSuicideLegals = false,true +hasButtons = false,false,true + +bSizes = 7,9,11,13,15,17,19, 8,10,12,14,16,18 +bSizeRelProbs = 1,4,3,10,7,9,35, 1,2,4,6,8,10 +allowRectangleProb = 0.00 + +komiAuto = True +komiStdev = 1.0 +handicapProb = 0.10 +handicapCompensateKomiProb = 0.50 +forkCompensateKomiProb = 0.80 +sgfCompensateKomiProb = 0.90 +komiBigStdevProb = 0.06 +komiBigStdev = 12.0 + +drawRandRadius = 0.5 +noResultStdev = 0.166666666 + +# Search limits----------------------------------------------------------------------------------- + +maxVisits = 600 +numSearchThreads = 1 + +# GPU Settings------------------------------------------------------------------------------- + +nnMaxBatchSize = 256 +nnCacheSizePowerOfTwo = 22 +nnMutexPoolSizePowerOfTwo = 16 +numNNServerThreadsPerModel = 2 +nnRandomize = true + +# CUDA GPU settings-------------------------------------- +# cudaDeviceToUse = 0 #use device 0 for all server threads (numNNServerThreadsPerModel) unless otherwise specified per-model or per-thread-per-model +# cudaDeviceToUseModel0 = 3 #use device 3 for model 0 for all threads unless otherwise specified per-thread for this model +# cudaDeviceToUseModel1 = 2 #use device 2 for model 1 for all threads unless otherwise specified per-thread for this model +# cudaDeviceToUseModel0Thread0 = 3 #use device 3 for model 0, server thread 0 +# cudaDeviceToUseModel0Thread1 = 2 #use device 2 for model 0, server thread 1 + +cudaDeviceToUseModel0Thread0 = 0 +cudaDeviceToUseModel0Thread1 = 1 + +cudaUseFP16 = auto +cudaUseNHWC = auto + +# Root move selection and biases------------------------------------------------------------------------------ + +chosenMoveTemperatureEarly = 0.75 +chosenMoveTemperatureHalflife = 19 +chosenMoveTemperature = 0.15 +chosenMoveSubtract = 0 +chosenMovePrune = 1 + +rootNoiseEnabled = true +rootDirichletNoiseTotalConcentration = 10.83 +rootDirichletNoiseWeight = 0.25 + +rootDesiredPerChildVisitsCoeff = 2 +rootNumSymmetriesToSample = 4 + +useLcbForSelection = true +lcbStdevs = 5.0 +minVisitPropForLCB = 0.15 + +# Internal params------------------------------------------------------------------------------ + +winLossUtilityFactor = 1.0 +staticScoreUtilityFactor = 0.00 +dynamicScoreUtilityFactor = 0.40 +dynamicScoreCenterZeroWeight = 0.25 +dynamicScoreCenterScale = 0.50 +noResultUtilityForWhite = 0.0 +drawEquivalentWinsForWhite = 0.5 + +rootEndingBonusPoints = 0.5 +rootPruneUselessMoves = true + +rootPolicyTemperatureEarly = 1.25 +rootPolicyTemperature = 1.1 + +cpuctExploration = 1.1 +cpuctExplorationLog = 0.0 +fpuReductionMax = 0.2 +rootFpuReductionMax = 0.0 +fpuUseParentAverage = true +valueWeightExponent = 0.5 + +mutexPoolSize = 64 +numVirtualLossesPerThread = 1 diff --git a/cpp/configs/training/gatekeeper1.cfg b/cpp/configs/training/gatekeeper1.cfg index 1c0cb75b4..c7d46d8d9 100644 --- a/cpp/configs/training/gatekeeper1.cfg +++ b/cpp/configs/training/gatekeeper1.cfg @@ -15,9 +15,9 @@ logToStdout = true # Match----------------------------------------------------------------------------------- -numGameThreads = 128 +numGameThreads = 50 maxMovesPerGame = 1600 -numGamesPerGating = 200 +numGamesPerGating = 50 allowResignation = true resignThreshold = -0.90 @@ -29,14 +29,14 @@ resignConsecTurns = 5 # Rules------------------------------------------------------------------------------------ -koRules = SIMPLE,POSITIONAL,SITUATIONAL +koRules = POSITIONAL scoringRules = AREA,TERRITORY -taxRules = NONE,NONE,SEKI,SEKI,ALL -multiStoneSuicideLegals = false,true -hasButtons = false,false,true +taxRules = NONE +multiStoneSuicideLegals = false +hasButtons = false -bSizes = 9,11,13,15,17,19, 10,12,14,16,18 -bSizeRelProbs = 2,3,10,7,9,35, 2,4,6,8,10 +bSizes = 7,9,11,13,15,17,19 +bSizeRelProbs = 3,5,5,10,7,5,35 allowRectangleProb = 0.00 # Play game on rectangular board with this probability komiAuto = True # Automatically adjust komi to what the neural nets think are fair diff --git a/cpp/configs/training/selfplay1.cfg b/cpp/configs/training/selfplay1.cfg index 91742dcf0..718cc26c6 100644 --- a/cpp/configs/training/selfplay1.cfg +++ b/cpp/configs/training/selfplay1.cfg @@ -83,14 +83,14 @@ maxMovesPerGame = 1600 # Rules------------------------------------------------------------------------------------ -koRules = SIMPLE,POSITIONAL,SITUATIONAL +koRules = POSITIONAL scoringRules = AREA,TERRITORY -taxRules = NONE,NONE,SEKI,SEKI,ALL -multiStoneSuicideLegals = false,true -hasButtons = false,false,true +taxRules = NONE +multiStoneSuicideLegals = false +hasButtons = false -bSizes = 7,9,11,13,15,17,19, 8,10,12,14,16,18 -bSizeRelProbs = 1,4,3,10,7,9,35, 1,2,4,6,8,10 +bSizes = 7,9,11,13,15,17,19 +bSizeRelProbs = 3,5,5,10,7,5,35 allowRectangleProb = 0.00 # Play game on rectangular board with this probability komiAuto = True # Automatically adjust komi to what the neural nets think are fair based on the empty board, but still apply komiStdev. diff --git a/cpp/game/board.cpp b/cpp/game/board.cpp index 1324a2c02..f1be2e58b 100644 --- a/cpp/game/board.cpp +++ b/cpp/game/board.cpp @@ -49,17 +49,39 @@ void Location::getAdjacentOffsets(short adj_offsets[8], int x_size) { adj_offsets[0] = -(x_size+1); adj_offsets[1] = -1; - adj_offsets[2] = 1; - adj_offsets[3] = (x_size+1); - adj_offsets[4] = -(x_size+1)-1; - adj_offsets[5] = -(x_size+1)+1; - adj_offsets[6] = (x_size+1)-1; - adj_offsets[7] = (x_size+1)+1; + adj_offsets[2] = 1; + adj_offsets[3] = (x_size+1); + + adj_offsets[4] = x_size*x_size-1; // (x_size-1)*(x_size+1) + adj_offsets[5] = x_size-1; // (x_size-1) + adj_offsets[6] = -x_size+1; // -(x_size-1) + adj_offsets[7] = -x_size*x_size+1; // -(x_size-1)*(x_size+1) +} + +void Location::getDiagonalOffsets(short diag_offsets[8], int x_size) +{ + diag_offsets[0] = -x_size-2; // -(x_size+1) - 1 + diag_offsets[1] = -x_size; // -(x_size+1) + 1 + diag_offsets[2] = x_size; // (x_size+1) - 1 + diag_offsets[3] = x_size+2; // (x_size+1) + 1 + + diag_offsets[4] = -x_size*x_size; // -(x_size-1)*(x_size+1) - 1 + diag_offsets[5] = -x_size*x_size+2; // -(x_size-1)*(x_size+1) + 1 + diag_offsets[6] = x_size*x_size-2; // (x_size-1)*(x_size+1) - 1 + diag_offsets[7] = x_size*x_size; // (x_size-1)*(x_size+1) + 1 } bool Location::isAdjacent(Loc loc0, Loc loc1, int x_size) { - return loc0 == loc1 - (x_size+1) || loc0 == loc1 - 1 || loc0 == loc1 + 1 || loc0 == loc1 + (x_size+1); + // return loc0 == loc1 - (x_size+1) || loc0 == loc1 - 1 || loc0 == loc1 + 1 || loc0 == loc1 + (x_size+1); + int x0 = Location::getX(loc0, x_size), y0 = Location::getY(loc0, x_size); + int x1 = Location::getX(loc1, x_size), y1 = Location::getY(loc1, x_size); + if(x0 == x1) + return y0 == y1-1 || y0 == y1+1 || (y0 == 0 && y1 == x_size-1) || (y0 == x_size-1 && y1 == 0); + else if(y0 == y1) + return x0 == x1-1 || x0 == x1+1 || (x0 == 0 && x1 == x_size-1) || (x0 == x_size-1 && x1 == 0); + else + return false; } Loc Location::getMirrorLoc(Loc loc, int x_size, int y_size) { @@ -91,12 +113,18 @@ bool Location::isNearCentral(Loc loc, int x_size, int y_size) { } -#define FOREACHADJ(BLOCK) {int ADJOFFSET = -(x_size+1); {BLOCK}; ADJOFFSET = -1; {BLOCK}; ADJOFFSET = 1; {BLOCK}; ADJOFFSET = x_size+1; {BLOCK}}; +#define FOREACHADJ(BLOCK) {int ADJOFFSET = -(x_size+1), ADJOFFSET2 = x_size*x_size-1; {BLOCK}; ADJOFFSET = -1; ADJOFFSET2 = x_size-1; {BLOCK}; ADJOFFSET = 1; ADJOFFSET2 = -x_size+1; {BLOCK}; ADJOFFSET = x_size+1; ADJOFFSET2 = -x_size*x_size+1; {BLOCK}}; + #define ADJ0 (-(x_size+1)) #define ADJ1 (-1) #define ADJ2 (1) #define ADJ3 (x_size+1) +#define ADJ0B (x_size*x_size-1) // (x_size-1)*(x_size+1) +#define ADJ1B (x_size-1) // (x_size-1) +#define ADJ2B (-x_size+1) // -(x_size-1) +#define ADJ3B (-x_size*x_size+1) // -(x_size-1)*(x_size+1) + //CONSTRUCTORS AND INITIALIZATION---------------------------------------------------------- Board::Board() @@ -127,6 +155,7 @@ Board::Board(const Board& other) numWhiteCaptures = other.numWhiteCaptures; memcpy(adj_offsets, other.adj_offsets, sizeof(short)*8); + memcpy(diag_offsets, other.diag_offsets, sizeof(short)*8); } void Board::init(int xS, int yS) @@ -157,6 +186,7 @@ void Board::init(int xS, int yS) numWhiteCaptures = 0; Location::getAdjacentOffsets(adj_offsets,x_size); + Location::getDiagonalOffsets(diag_offsets,x_size); } void Board::initHash() @@ -263,6 +293,7 @@ bool Board::isSuicide(Loc loc, Player pla) const Player opp = getOpp(pla); FOREACHADJ( Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == C_EMPTY) return false; @@ -287,6 +318,7 @@ bool Board::isIllegalSuicide(Loc loc, Player pla, bool isMultiStoneSuicideLegal) Player opp = getOpp(pla); FOREACHADJ( Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == C_EMPTY) return false; @@ -316,8 +348,9 @@ void Board::getBoundNumLibertiesAfterPlay(Loc loc, Player pla, int& lowerBound, int numConnectionLibs = 0; //Sum over friendly groups connected to of their libs-1 int maxConnectionLibs = 0; //Max over friendly groups connected to of their libs-1 - for(int i = 0; i < 4; i++) { - Loc adj = loc + adj_offsets[i]; + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == C_EMPTY) { numImmediateLibs++; } @@ -335,7 +368,7 @@ void Board::getBoundNumLibertiesAfterPlay(Loc loc, Player pla, int& lowerBound, if(connLibs > maxConnectionLibs) maxConnectionLibs = connLibs; } - } + ); lowerBound = numCaps + (maxConnectionLibs > numImmediateLibs ? maxConnectionLibs : numImmediateLibs); upperBound = numImmediateLibs + potentialLibsFromCaps + numConnectionLibs; @@ -353,8 +386,9 @@ int Board::getNumLibertiesAfterPlay(Loc loc, Player pla, int max) const Loc capturedGroupHeads[4]; //First, count immediate liberties and groups that would be captured - for(int i = 0; i < 4; i++) { - Loc adj = loc + adj_offsets[i]; + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == C_EMPTY) { libs[numLibs++] = adj; if(numLibs >= max) @@ -374,7 +408,7 @@ int Board::getNumLibertiesAfterPlay(Loc loc, Player pla, int max) const if(!alreadyFound) capturedGroupHeads[numCapturedGroups++] = head; } - } + ); auto wouldBeEmpty = [numCapturedGroups,&capturedGroupHeads,this,opp](Loc lc) { if(this->colors[lc] == C_EMPTY) @@ -390,8 +424,9 @@ int Board::getNumLibertiesAfterPlay(Loc loc, Player pla, int max) const //Next, walk through all stones of all surrounding groups we would connect with and count liberties, avoiding overlap. int numConnectingGroups = 0; Loc connectingGroupHeads[4]; - for(int i = 0; i<4; i++) { - Loc adj = loc + adj_offsets[i]; + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == pla) { Loc head = chain_head[adj]; bool alreadyFound = false; @@ -407,6 +442,7 @@ int Board::getNumLibertiesAfterPlay(Loc loc, Player pla, int max) const { for(int k = 0; k < 4; k++) { Loc possibleLib = cur + adj_offsets[k]; + if(colors[possibleLib] == C_WALL) possibleLib = cur + adj_offsets[k + 4]; if(possibleLib != loc && wouldBeEmpty(possibleLib)) { bool alreadyCounted = false; for(int l = 0; l= 2 || (against_wall && num_opp_corners >= 1)) + if(num_opp_corners >= 2 || num_opp_corners >= 1) return false; return true; @@ -506,6 +539,7 @@ bool Board::wouldBeCapture(Loc loc, Player pla) const { Player opp = getOpp(pla); FOREACHADJ( Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == opp) { if(getNumLiberties(adj) == 1) @@ -523,17 +557,17 @@ bool Board::wouldBeKoCapture(Loc loc, Player pla) const { //Check that surounding points are are all opponent owned and exactly one of them is capturable Player opp = getOpp(pla); Loc oppCapturableLoc = NULL_LOC; - for(int i = 0; i < 4; i++) - { - Loc adj = loc + adj_offsets[i]; - if(colors[adj] != C_WALL && colors[adj] != opp) + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; + if(colors[adj] != opp) return false; if(colors[adj] == opp && getNumLiberties(adj) == 1) { if(oppCapturableLoc != NULL_LOC) return false; oppCapturableLoc = adj; } - } + ); if(oppCapturableLoc == NULL_LOC) return false; @@ -549,17 +583,17 @@ Loc Board::getKoCaptureLoc(Loc loc, Player pla) const { //Check that surounding points are are all opponent owned and exactly one of them is capturable Player opp = getOpp(pla); Loc oppCapturableLoc = NULL_LOC; - for(int i = 0; i < 4; i++) - { - Loc adj = loc + adj_offsets[i]; - if(colors[adj] != C_WALL && colors[adj] != opp) + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; + if(colors[adj] != opp) return NULL_LOC; if(colors[adj] == opp && getNumLiberties(adj) == 1) { if(oppCapturableLoc != NULL_LOC) return NULL_LOC; oppCapturableLoc = adj; } - } + ); if(oppCapturableLoc == NULL_LOC) return NULL_LOC; @@ -572,6 +606,7 @@ Loc Board::getKoCaptureLoc(Loc loc, Player pla) const { bool Board::isAdjacentToPla(Loc loc, Player pla) const { FOREACHADJ( Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == pla) return true; ); @@ -579,10 +614,17 @@ bool Board::isAdjacentToPla(Loc loc, Player pla) const { } bool Board::isAdjacentOrDiagonalToPla(Loc loc, Player pla) const { - for(int i = 0; i<8; i++) { - Loc adj = loc + adj_offsets[i]; + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == pla) return true; + ); + for(int i = 0; i<4; i++) { + Loc diag = loc + diag_offsets[i]; + if(colors[diag] == C_WALL) diag = loc + diag_offsets[i + 4]; + if(colors[diag] == pla) + return true; } return false; } @@ -592,6 +634,7 @@ bool Board::isAdjacentToChain(Loc loc, Loc chain) const { return false; FOREACHADJ( Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == colors[chain] && chain_head[adj] == chain_head[chain]) return true; ); @@ -608,6 +651,7 @@ bool Board::isNonPassAliveSelfConnection(Loc loc, Player pla, Color* passAliveAr for(int i = 0; i < 4; i++) { Loc adj = loc + adj_offsets[i]; + if(colors[adj] == C_WALL) adj = loc + adj_offsets[i + 4]; if(colors[adj] == pla && passAliveArea[adj] == C_EMPTY) { nonPassAliveAdjHead = chain_head[adj]; break; @@ -617,12 +661,12 @@ bool Board::isNonPassAliveSelfConnection(Loc loc, Player pla, Color* passAliveAr if(nonPassAliveAdjHead == NULL_LOC) return false; - for(int i = 0; i < 4; i++) - { - Loc adj = loc + adj_offsets[i]; + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == pla && chain_head[adj] != nonPassAliveAdjHead) return true; - } + ); return false; } @@ -710,15 +754,19 @@ Board::MoveRecord Board::playMoveRecorded(Loc loc, Player pla) Player opp = getOpp(pla); { int adj = loc + ADJ0; + if(colors[adj] == C_WALL) adj = loc + ADJ0B; if(colors[adj] == opp && getNumLiberties(adj) == 1) record.capDirs |= (((uint8_t)1) << 0); } { int adj = loc + ADJ1; + if(colors[adj] == C_WALL) adj = loc + ADJ1B; if(colors[adj] == opp && getNumLiberties(adj) == 1) record.capDirs |= (((uint8_t)1) << 1); } { int adj = loc + ADJ2; + if(colors[adj] == C_WALL) adj = loc + ADJ2B; if(colors[adj] == opp && getNumLiberties(adj) == 1) record.capDirs |= (((uint8_t)1) << 2); } { int adj = loc + ADJ3; + if(colors[adj] == C_WALL) adj = loc + ADJ3B; if(colors[adj] == opp && getNumLiberties(adj) == 1) record.capDirs |= (((uint8_t)1) << 3); } @@ -745,6 +793,7 @@ void Board::undo(Board::MoveRecord record) for(int i = 0; i<4; i++) { int adj = loc + adj_offsets[i]; + if(colors[adj] == C_WALL) adj = loc + adj_offsets[i + 4]; if(record.capDirs & (1 << i)) { if(colors[adj] == C_EMPTY) { @@ -783,6 +832,7 @@ void Board::undo(Board::MoveRecord record) int numNeighbors = 0; FOREACHADJ( int adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == record.pla) numNeighbors++; ); @@ -823,6 +873,7 @@ void Board::undo(Board::MoveRecord record) int libertyDelta = 0; FOREACHADJ( int adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == C_EMPTY && !isLibertyOf(adj,head)) libertyDelta--; ); //Removing this stone itself added a liberty to the group though. @@ -843,12 +894,12 @@ void Board::undo(Board::MoveRecord record) } while (cur != loc); //Rebuild each chain adjacent now - for(int i = 0; i<4; i++) - { - int adj = loc + adj_offsets[i]; + FOREACHADJ( + int adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == record.pla && chain_head[adj] == NULL_LOC) rebuildChain(adj, record.pla); - } + ); } } } @@ -867,8 +918,9 @@ Hash128 Board::getPosHashAfterMove(Loc loc, Player pla) const { bool wouldBeSuicide = true; int numCapturedGroups = 0; Loc capturedGroupHeads[4]; - for(int i = 0; i < 4; i++) { - Loc adj = loc + adj_offsets[i]; + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == C_EMPTY) wouldBeSuicide = false; else if(colors[adj] == pla && getNumLiberties(adj) > 1) @@ -896,14 +948,15 @@ Hash128 Board::getPosHashAfterMove(Loc loc, Player pla) const { } } } - } + ); //Update hash for suicidal moves if(wouldBeSuicide) { assert(numCapturedGroups == 0); - for(int i = 0; i < 4; i++) { - Loc adj = loc + adj_offsets[i]; + FOREACHADJ( + int adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; //Suicide capture! if(colors[adj] == pla && getNumLiberties(adj) == 1) { //Make sure we haven't already counted it @@ -924,7 +977,7 @@ Hash128 Board::getPosHashAfterMove(Loc loc, Player pla) const { } while (cur != adj); } } - } + ); //Don't forget the stone we'd place would also die hash ^= ZOBRIST_BOARD_HASH[loc][pla]; @@ -964,6 +1017,7 @@ void Board::playMoveAssumeLegal(Loc loc, Player pla) for(int i = 0; i < 4; i++) { int adj = loc + adj_offsets[i]; + if(colors[adj] == C_WALL) adj = loc + adj_offsets[i + 4]; //Friendly chain! if(colors[adj] == pla) @@ -1031,10 +1085,10 @@ void Board::playMoveAssumeLegal(Loc loc, Player pla) int Board::getNumImmediateLiberties(Loc loc) const { int num_libs = 0; - if(colors[loc + ADJ0] == C_EMPTY) num_libs++; - if(colors[loc + ADJ1] == C_EMPTY) num_libs++; - if(colors[loc + ADJ2] == C_EMPTY) num_libs++; - if(colors[loc + ADJ3] == C_EMPTY) num_libs++; + if(colors[loc + ADJ0] == C_EMPTY || (colors[loc + ADJ0] == C_WALL && colors[loc + ADJ0B] == C_EMPTY)) num_libs++; + if(colors[loc + ADJ1] == C_EMPTY || (colors[loc + ADJ1] == C_WALL && colors[loc + ADJ1B] == C_EMPTY)) num_libs++; + if(colors[loc + ADJ2] == C_EMPTY || (colors[loc + ADJ2] == C_WALL && colors[loc + ADJ2B] == C_EMPTY)) num_libs++; + if(colors[loc + ADJ3] == C_EMPTY || (colors[loc + ADJ3] == C_WALL && colors[loc + ADJ3B] == C_EMPTY)) num_libs++; return num_libs; } @@ -1042,14 +1096,15 @@ int Board::getNumImmediateLiberties(Loc loc) const int Board::countHeuristicConnectionLibertiesX2(Loc loc, Player pla) const { int num_libsX2 = 0; - for(int i = 0; i < 4; i++) { - Loc adj = loc + adj_offsets[i]; + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == pla) { int libs = chain_data[chain_head[adj]].num_liberties; if(libs > 1) num_libsX2 += libs * 2 - 3; } - } + ); return num_libsX2; } @@ -1057,19 +1112,12 @@ int Board::countHeuristicConnectionLibertiesX2(Loc loc, Player pla) const //Assumes loc is empty bool Board::isLibertyOf(Loc loc, Loc head) const { - Loc adj; - adj = loc + ADJ0; - if(colors[adj] == colors[head] && chain_head[adj] == head) - return true; - adj = loc + ADJ1; - if(colors[adj] == colors[head] && chain_head[adj] == head) - return true; - adj = loc + ADJ2; - if(colors[adj] == colors[head] && chain_head[adj] == head) - return true; - adj = loc + ADJ3; - if(colors[adj] == colors[head] && chain_head[adj] == head) - return true; + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; + if(colors[adj] == colors[head] && chain_head[adj] == head) + return true; + ); return false; } @@ -1101,6 +1149,7 @@ void Board::mergeChains(Loc loc1, Loc loc2) //Any adjacent liberty is a new liberty for head1 if it is not adjacent to a stone of head1 FOREACHADJ( Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == C_EMPTY && !isLibertyOf(adj,head1)) numNewLiberties++; ); @@ -1212,12 +1261,12 @@ Loc Board::addChainHelper(Loc head, Loc tailTarget, Loc loc, Player pla) //Recursively add stones around us. Loc nextTailTarget = loc; - for(int i = 0; i<4; i++) - { - Loc adj = loc + adj_offsets[i]; + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == C_EMPTY) nextTailTarget = addChainHelper(head,nextTailTarget,adj,pla); - } + ); return nextTailTarget; } @@ -1249,6 +1298,11 @@ Loc Board::rebuildChainHelper(Loc head, Loc tailTarget, Loc loc, Player pla) Loc adj2 = loc + ADJ2; Loc adj3 = loc + ADJ3; + if(colors[adj0] == C_WALL) adj0 = loc + ADJ0B; + if(colors[adj1] == C_WALL) adj1 = loc + ADJ1B; + if(colors[adj2] == C_WALL) adj2 = loc + ADJ2B; + if(colors[adj3] == C_WALL) adj3 = loc + ADJ3B; + //Count new liberties int numHeadLibertiesToAdd = 0; if(colors[adj0] == C_EMPTY && !isLibertyOf(adj0,head)) numHeadLibertiesToAdd++; @@ -1279,6 +1333,11 @@ void Board::changeSurroundingLiberties(Loc loc, Player pla, int delta) Loc adj2 = loc + ADJ2; Loc adj3 = loc + ADJ3; + if(colors[adj0] == C_WALL) adj0 = loc + ADJ0B; + if(colors[adj1] == C_WALL) adj1 = loc + ADJ1B; + if(colors[adj2] == C_WALL) adj2 = loc + ADJ2B; + if(colors[adj3] == C_WALL) adj3 = loc + ADJ3B; + if(colors[adj0] == pla) chain_data[chain_head[adj0]].num_liberties += delta; if(colors[adj1] == pla @@ -1359,12 +1418,21 @@ void Board::changeSurroundingLiberties(Loc loc, Player pla, int delta) int Location::distance(Loc loc0, Loc loc1, int x_size) { int dx = getX(loc1,x_size) - getX(loc0,x_size); int dy = (loc1-loc0-dx) / (x_size+1); - return (dx >= 0 ? dx : -dx) + (dy >= 0 ? dy : -dy); + // return (dx >= 0 ? dx : -dx) + (dy >= 0 ? dy : -dy); + if(dx < 0) dx = -dx; + if(dx > x_size-dx) dx = x_size-dx; + if(dy < 0) dy = -dy; + if(dy > x_size-dy) dy = x_size-dy; + return dx + dy; } int Location::euclideanDistanceSquared(Loc loc0, Loc loc1, int x_size) { int dx = getX(loc1,x_size) - getX(loc0,x_size); int dy = (loc1-loc0-dx) / (x_size+1); + if(dx < 0) dx = -dx; + if(dx > x_size-dx) dx = x_size-dx; + if(dy < 0) dy = -dy; + if(dy > x_size-dy) dy = x_size-dy; return dx*dx + dy*dy; } @@ -1377,8 +1445,9 @@ int Board::findLiberties(Loc loc, vector& buf, int bufStart, int bufIdx) co Loc cur = loc; do { - for(int i = 0; i < 4; i++) { - Loc lib = cur + adj_offsets[i]; + FOREACHADJ( + Loc lib = cur + ADJOFFSET; + if(colors[lib] == C_WALL) lib = cur + ADJOFFSET2; if(colors[lib] == C_EMPTY) { //Check for dups bool foundDup = false; @@ -1395,7 +1464,7 @@ int Board::findLiberties(Loc loc, vector& buf, int bufStart, int bufIdx) co numFound++; } } - } + ); cur = next_in_chain[cur]; } while (cur != loc); @@ -1417,8 +1486,9 @@ int Board::findLibertyGainingCaptures(Loc loc, vector& buf, int bufStart, i Loc cur = loc; do { - for(int i = 0; i < 4; i++) { - Loc adj = cur + adj_offsets[i]; + FOREACHADJ( + Loc adj = cur + ADJOFFSET; + if(colors[adj] == C_WALL) adj = cur + ADJOFFSET2; if(colors[adj] == opp) { Loc head = chain_head[adj]; if(chain_data[head].num_liberties == 1) { @@ -1436,7 +1506,7 @@ int Board::findLibertyGainingCaptures(Loc loc, vector& buf, int bufStart, i } } } - } + ); cur = next_in_chain[cur]; } while (cur != loc); @@ -1452,6 +1522,7 @@ bool Board::hasLibertyGainingCaptures(Loc loc) const { { FOREACHADJ( Loc adj = cur + ADJOFFSET; + if(colors[adj] == C_WALL) adj = cur + ADJOFFSET2; if(colors[adj] == opp) { Loc head = chain_head[adj]; if(chain_data[head].num_liberties == 1) @@ -1931,6 +2002,7 @@ void Board::calculateAreaForPla( //Push adjacent locations on to queue. FOREACHADJ( Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if((colors[adj] == C_EMPTY || colors[adj] == opp) && regionIdxByLoc[adj] == -1) { buildRegionQueue[buildRegionQueueTail] = adj; buildRegionQueueTail += 1; @@ -1969,8 +2041,9 @@ void Board::calculateAreaForPla( uint16_t vStart = vitalStart[regionIdx]; assert(vStart + 4 <= vitalForPlaHeadsListsMaxLen); uint16_t initialVLen = 0; - for(int i = 0; i<4; i++) { - Loc adj = loc + adj_offsets[i]; + FOREACHADJ( + Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == pla) { Loc plaHead = chain_head[adj]; bool alreadyPresent = false; @@ -1985,7 +2058,7 @@ void Board::calculateAreaForPla( initialVLen += 1; } } - } + ); vitalLen[regionIdx] = initialVLen; } Loc tailTarget = buildRegion(loc,regionIdx); //buildRegion uses loc itself as the head @@ -2040,8 +2113,9 @@ void Board::calculateAreaForPla( //Walk the pla chain to update bordering regions Loc cur = plaHead; do { - for(int j = 0; j<4; j++) { - Loc adj = cur + adj_offsets[j]; + FOREACHADJ( + Loc adj = cur + ADJOFFSET; + if(colors[adj] == C_WALL) adj = cur + ADJOFFSET2; int16_t regionIdx = regionIdxByLoc[adj]; //Mark regions as no longer vital if(regionIdx >= 0 && !bordersNonPassAlivePlaByHead[regionHeads[regionIdx]] && (colors[adj] == C_EMPTY || colors[adj] == opp)) { @@ -2054,7 +2128,7 @@ void Board::calculateAreaForPla( vitalCountByPlaHead[plaH] -= 1; } } - } + ); cur = next_in_chain[cur]; } while (cur != plaHead); } @@ -2134,14 +2208,23 @@ void Board::calculateIndependentLifeAreaHelper( for(int x = 0; x < x_size; x++) { Loc loc = Location::getLoc(x,y,x_size); if(basicArea[loc] != C_EMPTY && !isSeki[loc]) { + Loc adj0 = loc+ADJ0; + Loc adj1 = loc+ADJ1; + Loc adj2 = loc+ADJ2; + Loc adj3 = loc+ADJ3; + if(colors[adj0] == C_WALL) adj0 = loc+ADJ0B; + if(colors[adj1] == C_WALL) adj1 = loc+ADJ1B; + if(colors[adj2] == C_WALL) adj2 = loc+ADJ2B; + if(colors[adj3] == C_WALL) adj3 = loc+ADJ3B; + if( //Stone of player owning the area is in atari? Treat as seki. (colors[loc] == basicArea[loc] && getNumLiberties(loc) == 1) || //Touches dame? Treat as seki - ((colors[loc+ADJ0] == C_EMPTY && basicArea[loc+ADJ0] == C_EMPTY) || - (colors[loc+ADJ1] == C_EMPTY && basicArea[loc+ADJ1] == C_EMPTY) || - (colors[loc+ADJ2] == C_EMPTY && basicArea[loc+ADJ2] == C_EMPTY) || - (colors[loc+ADJ3] == C_EMPTY && basicArea[loc+ADJ3] == C_EMPTY)) + ((colors[adj0] == C_EMPTY && basicArea[adj0] == C_EMPTY) || + (colors[adj1] == C_EMPTY && basicArea[adj1] == C_EMPTY) || + (colors[adj2] == C_EMPTY && basicArea[adj2] == C_EMPTY) || + (colors[adj3] == C_EMPTY && basicArea[adj3] == C_EMPTY)) ) { Player pla = basicArea[loc]; isSeki[loc] = true; @@ -2153,6 +2236,7 @@ void Board::calculateIndependentLifeAreaHelper( //Look all around it, floodfill FOREACHADJ( Loc adj = nextLoc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = nextLoc + ADJOFFSET2; if(basicArea[adj] == pla && !isSeki[adj]) { isSeki[adj] = true; queue[queueTail++] = adj; @@ -2184,6 +2268,7 @@ void Board::calculateIndependentLifeAreaHelper( //Look all around it, floodfill FOREACHADJ( Loc adj = nextLoc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = nextLoc + ADJOFFSET2; if(basicArea[adj] == pla && result[adj] != basicArea[adj]) { result[adj] = basicArea[adj]; queue[queueTail++] = adj; @@ -2697,6 +2782,7 @@ Board Board::ofJson(const nlohmann::json& data) { bool Board::isAdjacentToPlaHead(Player pla, Loc loc, Loc plaHead) const { FOREACHADJ( Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == pla && chain_head[adj] == plaHead) return true; ); @@ -2722,6 +2808,7 @@ bool Board::countEmptyHelper(bool* emptyCounted, Loc initialLoc, int& count, int Loc loc = toExpand[numExpanded++]; FOREACHADJ( Loc adj = loc + ADJOFFSET; + if(colors[adj] == C_WALL) adj = loc + ADJOFFSET2; if(colors[adj] == C_EMPTY && !emptyCounted[adj]) { count += 1; emptyCounted[adj] = true; @@ -2757,13 +2844,14 @@ bool Board::simpleRepetitionBoundGt(Loc loc, int bound) const { else { Loc cur = loc; do { - for(int i = 0; i < 4; i++) { - Loc lib = cur + adj_offsets[i]; + FOREACHADJ( + int lib = cur + ADJOFFSET; + if(colors[lib] == C_WALL) lib = cur + ADJOFFSET2; if(colors[lib] == C_EMPTY) { if(countEmptyHelper(emptyCounted, lib, count, bound)) return true; } - } + ); cur = next_in_chain[cur]; } while (cur != loc); } diff --git a/cpp/game/board.h b/cpp/game/board.h index bb6d2f6dd..61d0d9580 100644 --- a/cpp/game/board.h +++ b/cpp/game/board.h @@ -54,6 +54,7 @@ namespace Location int getY(Loc loc, int x_size); void getAdjacentOffsets(short adj_offsets[8], int x_size); + void getDiagonalOffsets(short diag_offsets[8], int x_size); bool isAdjacent(Loc loc0, Loc loc1, int x_size); Loc getMirrorLoc(Loc loc, int x_size, int y_size); Loc getCenterLoc(int x_size, int y_size); @@ -312,7 +313,8 @@ struct Board int numBlackCaptures; //Number of b stones captured, informational and used by board history when clearing pos int numWhiteCaptures; //Number of w stones captured, informational and used by board history when clearing pos - short adj_offsets[8]; //Indices 0-3: Offsets to add for adjacent points. Indices 4-7: Offsets for diagonal points. 2 and 3 are +x and +y. + short adj_offsets[8]; //Indices 0-3: Offsets to add for regular adjacent points. Indices 4-7: Offsets for border adjacent points. + short diag_offsets[8]; //Offsets to add for diagonal points. private: void init(int xS, int yS); diff --git a/cpp/neuralnet/nninputs.cpp b/cpp/neuralnet/nninputs.cpp index 52c923e60..aeed6c54e 100644 --- a/cpp/neuralnet/nninputs.cpp +++ b/cpp/neuralnet/nninputs.cpp @@ -282,6 +282,7 @@ void NNInputs::fillScoring( //Push adjacent locations on to queue for(int i = 0; i<4; i++) { Loc adj = next + board.adj_offsets[i]; + if(area[adj] == C_WALL) adj = next + board.adj_offsets[i + 4]; if(area[adj] == areaColor && !visited[adj]) { queue[queueTail] = adj; queueTail++; diff --git a/cpp/neuralnet/openclkernels.cpp b/cpp/neuralnet/openclkernels.cpp index 8e2be503f..b77052276 100644 --- a/cpp/neuralnet/openclkernels.cpp +++ b/cpp/neuralnet/openclkernels.cpp @@ -152,12 +152,27 @@ __kernel void conv2dNCHW( for(int dic = 0; dic= ySize) { + iy -= ySize; + } for(int itx = lx; itx= 0 && iy < ySize && ix >= 0 && ix < xSize) { - inputValue = INPUT(n,icBase+dic,iy,ix); + // For Weiqi/Go + // if(iy >= 0 && iy < ySize && ix >= 0 && ix < xSize) { + // inputValue = INPUT(n,icBase+dic,iy,ix); + // } + + // For Daoqi + if (ix < 0) { + ix += xSize; + } else if (ix >= xSize) { + ix -= xSize; } + inputValue = INPUT(n,icBase+dic,iy,ix); INPUTTILE(dic,ity,itx) = inputValue; } } @@ -253,10 +268,28 @@ __kernel void transform( //Copy input into private tile for(int subY = 0; subY < INTILE_YSIZE; subY++) { int y = tileY * OUTTILE_YSIZE + subY + INTILE_YOFFSET; + // For Daoqi + if(y < 0) { + y += ySize; + } else if (y >= ySize) { + y -= ySize; + } for(int subX = 0; subX < INTILE_XSIZE; subX++) { int x = tileX * OUTTILE_XSIZE + subX + INTILE_XOFFSET; real value = ZERO; - if(y >= 0 && y < ySize && x >= 0 && x < xSize && n < nSize && ic < icSize) { + // For Weiqi/Go + // if(y >= 0 && y < ySize && x >= 0 && x < xSize && n < nSize && ic < icSize) { + // int xy = y * xSize + x; + // value = INPUT(nic,xy); + // } + + // For Daoqi + if(n < nSize && ic < icSize) { + if (x < 0) { + x += xSize; + } else if (x >= xSize) { + x -= xSize; + } int xy = y * xSize + x; value = INPUT(nic,xy); } @@ -443,10 +476,28 @@ __kernel void bnReluTransform( //Copy input into private tile for(int subY = 0; subY < INTILE_YSIZE; subY++) { int y = tileY * OUTTILE_YSIZE + subY + INTILE_YOFFSET; + // For Daoqi + if(y < 0) { + y += ySize; + } else if (y >= ySize) { + y -= ySize; + } for(int subX = 0; subX < INTILE_XSIZE; subX++) { int x = tileX * OUTTILE_XSIZE + subX + INTILE_XOFFSET; real value = ZERO; - if(y >= 0 && y < ySize && x >= 0 && x < xSize && n < nSize && ic < icSize) { + // For Weiqi/Go + // if(y >= 0 && y < ySize && x >= 0 && x < xSize && n < nSize && ic < icSize) { + // int xy = y * xSize + x; + // value = fmax(INPUT(nic,xy) * scale[ic] + bias[ic], 0.0f) * mask[n * xySize + xy]; + // } + + // For Daoqi + if(n < nSize && ic < icSize) { + if (x < 0) { + x += xSize; + } else if (x >= xSize) { + x -= xSize; + } int xy = y * xSize + x; value = fmax(INPUT(nic,xy) * LOAD(scale,ic) + LOAD(bias,ic), ZERO) * LOAD(mask, n * xySize + xy); } @@ -719,12 +770,30 @@ __kernel void untransform( //Copy into output for(int subY = 0; subY < OUTTILE_YSIZE; subY++) { int y = tileY * OUTTILE_YSIZE + subY; + // For Daoqi + // if(y < 0) { + // y += ySize; + // } else if (y >= ySize) { + // y -= ySize; + // } for(int subX = 0; subX < OUTTILE_XSIZE; subX++) { int x = tileX * OUTTILE_XSIZE + subX; + // For Weiqi/Go if(y >= 0 && y < ySize && x >= 0 && x < xSize && tileX < numTilesX && tileY < numTilesY && n < nSize) { real result = WTILE(subY,subX); WRITEOUTPUT(noc,y,x,result); } + + // For Daoqi + // if(tileX < numTilesX && tileY < numTilesY && n < nSize) { + // if (x < 0) { + // x += xSize; + // } else if (x >= xSize) { + // x -= xSize; + // } + // real result = WTILE(subY,subX); + // WRITEOUTPUT(noc,y,x,result); + // } } } diff --git a/cpp/search/searchhelpers.cpp b/cpp/search/searchhelpers.cpp index 46af66f3f..d1809ec71 100644 --- a/cpp/search/searchhelpers.cpp +++ b/cpp/search/searchhelpers.cpp @@ -434,6 +434,7 @@ bool Search::shouldSuppressPass(const SearchNode* n) const { bool adjToPlaOwned = false; for(int j = 0; j<4; j++) { Loc adj = moveLoc + rootBoard.adj_offsets[j]; + if(rootBoard.colors[adj] == C_WALL) adj = moveLoc + rootBoard.adj_offsets[j + 4]; int adjPos = NNPos::locToPos(adj,rootBoard.x_size,nnXLen,nnYLen); double adjPlaOwnership = rootPla == P_WHITE ? whiteOwnerMap[adjPos] : -whiteOwnerMap[adjPos]; if(adjPlaOwnership > extreme) { diff --git a/cpp/tests/testcommon.cpp b/cpp/tests/testcommon.cpp index d353bc356..e92e25e07 100644 --- a/cpp/tests/testcommon.cpp +++ b/cpp/tests/testcommon.cpp @@ -19,7 +19,7 @@ string TestCommon::getBenchmarkSGFData(int boardSize) { static_assert(MAX_BENCHMARK_SGF_DATA_SIZE == 19, "TestCommon::getBenchmarkSGFData MAX_BENCHMARK_SGF_DATA_SIZE == 19"); if(boardSize == 19) { - sgfData = "(;FF[4]GM[1]SZ[19]HA[0]KM[7.5];B[dd];W[pp];B[dp];W[pd];B[qq];W[pq];B[qp];W[qo];B[ro];W[rn];B[qn];W[po];B[rm];W[rp];B[sn];W[rq];B[nc];W[oc];B[qr];W[rr];B[nd];W[pf];B[re];W[lc];B[jc];W[le];B[qc];W[qd];B[ob];W[pc];B[pb];W[rc];B[qb];W[rd];B[lb];W[mb];B[ld];W[kb];B[kc];W[la];B[mc];W[qi];B[ke];W[cc];B[dc];W[cd];B[cf];W[ce];B[de];W[bf];B[cb];W[bb];B[be];W[db];B[bc];W[ca];B[bd];W[cb];B[bg];W[df];B[cg];W[fc];B[nr];W[nq];B[pg];W[qg];B[of];W[ef];B[mq];W[cq];B[dq];W[cp];B[co];W[bo];B[bn];W[cn];B[do];W[bm];B[bp];W[an];B[bq];W[cr];B[br];W[kq];B[iq];W[ko];B[kr];W[cj];B[lq];W[bi];B[qj];W[ph];B[og];W[rf];B[gc];W[gd];B[hc];W[nb];B[rb];W[sb];B[lb];W[fe];B[ri];W[rh];B[pi];W[qh];B[af];W[lc];B[dn];W[dm];B[em];W[in];B[lb];W[fn];B[lc];W[en];B[fp];W[fm];B[pj];W[hp];B[hq];W[ij];B[di];W[dj];B[jl];W[il];B[jm];W[im];B[jk];W[jj];B[kj];W[ki];B[lj];W[li];B[mi];W[jh];B[pn];W[or];B[np];W[ie];B[dh];W[ei];B[eh];W[fh];B[kn];W[oo];B[mn];W[oh];B[ni];W[oq];B[fg];W[gh];B[hg];W[hh];B[ig];W[ih];B[fb];W[eb];B[gb];W[mj];B[mg];W[kf];B[lf];W[je];B[kg];W[kd];B[jg];W[ln];B[lm];W[lo];B[nn];W[ra];B[na];W[gp];B[gq];W[fo];B[rj];W[oe];B[me];W[mr];B[lr];W[ns];B[sp];W[on];B[om];W[pm];B[nm];W[ip];B[jp];W[jo];B[kp];W[lp];B[jq];W[ap];B[dr];W[ar];B[cs];W[aq];B[sr];W[qs];B[ab];W[ea];B[ec];W[fd];B[fa];W[ee];B[ke];W[oi];B[oj];W[le];B[ik];W[hk];B[ke];W[ep];B[fq];W[le];B[eo];W[ke];B[sq];W[pr];B[qm];W[ml];B[mk];W[lk];B[nj];W[kk];B[mj];W[km];B[kl];W[qa];B[hd];W[he];B[gf];W[ge];B[bh];W[nf];B[ng];W[ne];B[ci];W[ai];B[mf];W[eg];B[gg];W[dg];B[ch];W[si];B[ah];W[bj];B[sj];W[sh];B[ma];W[sc];B[pa];W[id];B[ic];W[ls];B[ks];W[ms];B[sa];W[ra];B[od];W[pe];B[kh];W[lh];B[ji];W[ii];B[mh];W[ji];B[lg];W[mp];B[no];W[jn];B[km];W[as];B[fi];W[ej])"; + sgfData = "(;FF[4]GM[1]SZ[19]HA[0]KM[7.5];B[dd];W[pp];B[dp];W[pd];B[qq];W[pq];B[qp];W[qo];B[ro];W[rn];B[qn];W[po];B[rm];W[rp];B[sn];W[rq];B[nc];W[oc];B[qr];W[rr];B[nd];W[pf];B[re];W[lc];B[jc];W[le];B[qc];W[qd];B[ob];W[pc];B[pb];W[rc];B[qb];W[rd];B[lb];W[mb];B[ld];W[kb];B[kc];W[la];B[mc];W[qi];B[ke];W[cc];B[dc];W[cd];B[cf];W[ce];B[de];W[bf];B[cb];W[bb];B[be];W[db];B[bc];W[ca];B[bd];W[cb];B[bg];W[df];B[cg];W[fc];B[nr];W[nq];B[pg];W[qg];B[of];W[ef];B[mq];W[cq];B[dq];W[cp];B[co];W[bo];B[bn];W[cn];B[do];W[bm];B[bp];W[an];B[bq];W[cr];B[br];W[kq];B[iq];W[ko];B[kr];W[cj];B[lq];W[bi];B[qj];W[ph];B[og];W[rf];B[gc];W[gd];B[hc];W[nb];B[rb];W[sb];B[lb];W[fe];B[ri];W[rh];B[pi];W[qh];B[af];W[lc];B[dn];W[dm];B[em];W[in];B[lb];W[fn];B[lc];W[en];B[fp];W[fm];B[pj];W[hp];B[hq];W[ij];B[di];W[dj];B[jl];W[il];B[jm];W[im];B[jk];W[jj];B[kj];W[ki];B[lj];W[li];B[mi];W[jh];B[pn];W[or];B[np];W[ie];B[dh];W[ei];B[eh];W[fh];B[kn];W[oo];B[mn];W[oh];B[ni];W[oq];B[fg];W[gh];B[hg];W[hh];B[ig];W[ih];B[fb];W[eb];B[gb];W[mj];B[mg];W[kf];B[lf];W[je];B[kg];W[kd];B[jg];W[ln];B[lm];W[lo];B[nn];W[ra];B[na];W[gp];B[gq];W[fo];B[rj];W[oe];B[me];W[mr];B[lr];W[ns];B[sp];W[on];B[om];W[pm];B[nm];W[ip];B[jp];W[jo];B[kp];W[lp];B[jq];W[ap];B[dr];W[ar];B[cs];W[aq];B[sr];W[qs];B[ab];W[ea];B[ec];W[fd];B[fa];W[ee];B[ke];W[oi];B[oj];W[le];B[ik];W[hk];B[ke];W[ep];B[fq];W[le];B[eo];W[ke];B[sq];W[pr];B[qm];W[ml];B[mk];W[lk];B[nj];W[kk];B[mj];W[km];B[kl];W[qa];B[hd];W[he];B[gf];W[ge];B[bh];W[nf];B[ng];W[ne];B[ci];W[ai];B[mf];W[eg];B[gg];W[dg];B[ch];W[si];B[ah];W[bj];B[sj];W[sh];B[ma];W[sc];B[pa];W[id];B[ic];W[ls];B[ks];W[ms];B[sa])"; } else if(boardSize == 18) { sgfData = "(;FF[4]GM[1]SZ[18]HA[0]KM[7.5];B[od];W[do];B[oo];W[dd];B[cp];W[dp];B[co];W[cn];B[bn];W[bm];B[cm];W[dn];B[bl];W[bo];B[am];W[bp];B[cq];W[bq];B[fc];W[pp];B[op];W[po];B[pm];W[pn];B[on];W[qm];B[pq];W[pc];B[qn];W[rn];B[ql];W[qo];B[pl];W[qq];B[pd];W[oc];B[nd];W[mb];B[cf];W[fd];B[gd];W[fe];B[dc];W[cc];B[ec];W[df];B[ed];W[de];B[cb];W[bd];B[bc];W[cd];B[ch];W[bb];B[ba];W[ac];B[lc];W[lb];B[kc];W[mk];B[lm];W[mi];B[lj];W[mj];B[kl];W[mm];B[mn];W[nm];B[pi];W[ln];B[kn];W[lo];B[om];W[ko];B[jo];W[jp];B[io];W[ip];B[ho];W[hp];B[kh];W[oi];B[ph];W[oh];B[go];W[gp];B[fp];W[fq];B[fo];W[lq];B[eq];W[dq];B[mp];W[pg];B[og];W[ng];B[of];W[pj];B[qj];W[qi];B[qh];W[qk];B[ri];W[pk];B[rk];W[ki];B[li];W[lh];B[kj];W[ji];B[jj];W[kg];B[jh];W[ii];B[jg];W[kf];B[gq];W[hq];B[hh];W[mg];B[fr];W[lp];B[eh];W[dm];B[nk];W[oj];B[rl];W[dk];B[hf];W[gg];B[gh];W[fg];B[fh];W[db];B[da];W[ca];B[nq];W[ea];B[rp];W[rq];B[rm];W[qr];B[qn];W[cj];B[bj];W[qm];B[qc];W[qb];B[qn];W[pr];B[oq];W[qm];B[qd];W[gl];B[fm];W[gj];B[el];W[dl];B[ek];W[ej];B[fj];W[ij];B[il];W[bi];B[bk];W[ci];B[cl];W[ih];B[ig];W[fk];B[fi];W[ik];B[jk];W[im];B[jl];W[jn];B[hm];W[km];B[ll];W[ml];B[in];W[qn];B[lf];W[le];B[mf];W[lg];B[kb];W[hg];B[je];W[ie];B[he];W[id];B[if];W[gc];B[hd];W[gb];B[ol];W[jd];B[hc];W[kd];B[hb];W[md];B[mc];W[nb];B[me];W[jf];B[rb];W[oa];B[fb];W[cg];B[bh];W[bg];B[dh];W[ah];B[mo];W[ld];B[nf];W[jb];B[jc];W[ka];B[ic];W[ke];B[fl];W[eb];B[ga];W[hr];B[ia];W[ja];B[qa];W[pb];B[gk];W[kn];B[aj];W[ai];B[nj];W[ni];B[jm];W[ao];B[an];W[nn];B[no];W[lk];B[dr];W[cr];B[en];W[er];B[ib];W[la];B[dr];W[mr];B[jq];W[jr];B[kr];W[kq];B[ir];W[iq];B[mq];W[jr];B[er];W[pa];B[rc];W[ra])"; diff --git a/cpp/tests/testsearch.cpp b/cpp/tests/testsearch.cpp index 1c86d2351..5f1636b7c 100644 --- a/cpp/tests/testsearch.cpp +++ b/cpp/tests/testsearch.cpp @@ -55,55 +55,55 @@ static void runBasicPositions(NNEvaluator* nnEval, Logger& logger) cout << endl << endl; } - { - cout << "GAME 3 ==========================================================================" << endl; - cout << "Extremely close botvbot game" << endl; - cout << endl; - - string sgfStr = "(;FF[4]GM[1]SZ[19]PB[v49-140-400v-fp16]PW[v49-140-400v-fp16-fpu25]HA[0]KM[7.5]RU[koPOSITIONALscoreAREAsui1]RE[W+0.5];B[qd];W[dp];B[cq];W[dq];B[cp];W[co];B[bo];W[bn];B[cn];W[do];B[bm];W[bp];B[an];W[bq];B[cd];W[qp];B[oq];W[pn];B[nd];W[ec];B[df];W[hc];B[jc];W[cb];B[lq];W[ch];B[cj];W[eh];B[gd];W[gc];B[fd];W[hd];B[gf];W[cl];B[dn];W[el];B[eo];W[fp];B[ej];W[bl];B[bk];W[al];B[cr];W[br];B[fi];W[gl];B[gn];W[gp];B[dk];W[dl];B[fm];W[fl];B[ho];W[iq];B[ip];W[jq];B[jp];W[hp];B[in];W[fh];B[gh];W[gg];B[gi];W[hg];B[fg];W[hf];B[eg];W[il];B[ii];W[kl];B[lo];W[jj];B[ql];W[pq];B[op];W[rm];B[ji];W[ki];B[kh];W[li];B[ij];W[gm];B[dc];W[eb];B[fn];W[jk];B[lk];W[ln];B[ll];W[km];B[mn];W[ko];B[kp];W[mo];B[lp];W[mm];B[nn];W[lm];B[on];W[nk];B[qn];W[qm];B[po];W[rn];B[pm];W[ed];B[cf];W[ni];B[rq];W[rp];B[pr];W[qq];B[qr];W[rr];B[rs];W[sr];B[lc];W[rd];B[rc];W[re];B[pd];W[rb];B[qc];W[qg];B[oj];W[ok];B[pk];W[sc];B[qb];W[bc];B[qh];W[ph];B[qi];W[og];B[kr];W[ff];B[ef];W[fe];B[bd];W[rg];B[oi];W[nh];B[pf];W[pg];B[is];W[hr];B[hs];W[gs];B[gr];W[js];B[fs];W[ir];B[ep];W[eq];B[fq];W[cs];B[er];W[dr];B[fo];W[gq];B[go];W[fr];B[ie];W[he];B[fq];W[hq];B[ib];W[bj];B[bi];W[aj];B[ai];W[ak];B[ci];W[rl];B[ee];W[ge];B[rk];W[ol];B[pl];W[mf];B[nf];W[of];B[ne];W[mg];B[qf];W[rf];B[hb];W[ad];B[jr];W[gs];B[hs];W[es];B[ds];W[fr];B[cc];W[bb];B[fq];W[es];B[ae];W[ac];B[ds];W[fr];B[lh];W[mh];B[fq];W[es];B[qo];W[ro];B[ds];W[fr];B[nj];W[mj];B[fq];W[es];B[bf];W[pi];B[pj];W[ri];B[qj];W[rh];B[gb];W[fb];B[ra];W[sb];B[le];W[me];B[md];W[rj];B[jf];W[sk];B[ks];W[fr];B[lf];W[if];B[id];W[ic];B[je];W[em];B[en];W[ck];B[ga];W[ek];B[dj];W[is];B[kq];W[gs];B[nm];W[nl];B[hk];W[im];B[jn];W[kn];B[gk];W[fk];B[db];W[da];B[fa];W[ea];B[jm];W[jl];B[ih];W[ig];B[jg];W[lg];B[kg];W[oe];B[od];W[dd];B[fj];W[ce];B[be];W[de];B[hh];W[jo];B[io];W[om];B[ap];W[aq];B[ao];W[qk];B[pn];W[am];B[bn];W[pp];B[rk];W[qe];B[pe];W[qk];B[sd];W[se];B[rk];W[qa];B[pa];W[qk];B[hl];W[hm];B[rk];W[jb];B[kb];W[qk];B[qs];W[rk];B[or];W[oh];B[ss];W[sq];B[ng];W[ik];B[hn];W[dm];B[cm];W[sd];B[qa];W[sa];B[kj];W[mk];B[ml];W[mi];B[ja];W[lj];B[dh];W[kk];B[ei];W[fc];B[bh];W[];B[cg];W[];B[no];W[];B[mp];W[];B[])"; - - runBotOnSgf(bot, sgfStr, rules, 191, 7.5, opts); - runBotOnSgf(bot, sgfStr, rules, 197, 7.5, opts); - runBotOnSgf(bot, sgfStr, rules, 330, 7.5, opts); - runBotOnSgf(bot, sgfStr, rules, 330, 7.0, opts); - - cout << endl; - cout << "Jigo and drawUtility===================" << endl; - cout << "(Game almost over, just a little cleanup)" << endl; - SearchParams testParams = params; - testParams.drawEquivalentWinsForWhite = 0.7; - cout << "testParams.drawEquivalentWinsForWhite = 0.7" << endl; - cout << endl; - - bot->setParams(testParams); - cout << "Komi 7.5 (white wins by 0.5)" << endl; - runBotOnSgf(bot, sgfStr, rules, 330, 7.5, opts); - cout << endl; - - cout << "Komi 7.0 (draw)" << endl; - runBotOnSgf(bot, sgfStr, rules, 330, 7.0, opts); - bot->setParams(params); - - cout << endl; - cout << "Consecutive searches playouts and visits===================" << endl; - cout << "Doing three consecutive searches by visits" << endl; - cout << endl; - TestSearchOptions opts2 = opts; - opts2.numMovesInARow = 3; - opts2.printAfterBegun = true; - runBotOnSgf(bot, sgfStr, rules, 85, 7.5, opts2); - cout << endl; - - cout << "Doing three consecutive searches by playouts (limit 200)" << endl; - cout << endl; - testParams = params; - testParams.maxPlayouts = 200; - testParams.maxVisits = 10000; - bot->setParams(testParams); - runBotOnSgf(bot, sgfStr, rules, 85, 7.5, opts2); - bot->setParams(params); - cout << endl << endl; - } + // { + // cout << "GAME 3 ==========================================================================" << endl; + // cout << "Extremely close botvbot game" << endl; + // cout << endl; + + // string sgfStr = "(;FF[4]GM[1]SZ[19]PB[v49-140-400v-fp16]PW[v49-140-400v-fp16-fpu25]HA[0]KM[7.5]RU[koPOSITIONALscoreAREAsui1]RE[W+0.5];B[qd];W[dp];B[cq];W[dq];B[cp];W[co];B[bo];W[bn];B[cn];W[do];B[bm];W[bp];B[an];W[bq];B[cd];W[qp];B[oq];W[pn];B[nd];W[ec];B[df];W[hc];B[jc];W[cb];B[lq];W[ch];B[cj];W[eh];B[gd];W[gc];B[fd];W[hd];B[gf];W[cl];B[dn];W[el];B[eo];W[fp];B[ej];W[bl];B[bk];W[al];B[cr];W[br];B[fi];W[gl];B[gn];W[gp];B[dk];W[dl];B[fm];W[fl];B[ho];W[iq];B[ip];W[jq];B[jp];W[hp];B[in];W[fh];B[gh];W[gg];B[gi];W[hg];B[fg];W[hf];B[eg];W[il];B[ii];W[kl];B[lo];W[jj];B[ql];W[pq];B[op];W[rm];B[ji];W[ki];B[kh];W[li];B[ij];W[gm];B[dc];W[eb];B[fn];W[jk];B[lk];W[ln];B[ll];W[km];B[mn];W[ko];B[kp];W[mo];B[lp];W[mm];B[nn];W[lm];B[on];W[nk];B[qn];W[qm];B[po];W[rn];B[pm];W[ed];B[cf];W[ni];B[rq];W[rp];B[pr];W[qq];B[qr];W[rr];B[rs];W[sr];B[lc];W[rd];B[rc];W[re];B[pd];W[rb];B[qc];W[qg];B[oj];W[ok];B[pk];W[sc];B[qb];W[bc];B[qh];W[ph];B[qi];W[og];B[kr];W[ff];B[ef];W[fe];B[bd];W[rg];B[oi];W[nh];B[pf];W[pg];B[is];W[hr];B[hs];W[gs];B[gr];W[js];B[fs];W[ir];B[ep];W[eq];B[fq];W[cs];B[er];W[dr];B[fo];W[gq];B[go];W[fr];B[ie];W[he];B[fq];W[hq];B[ib];W[bj];B[bi];W[aj];B[ai];W[ak];B[ci];W[rl];B[ee];W[ge];B[rk];W[ol];B[pl];W[mf];B[nf];W[of];B[ne];W[mg];B[qf];W[rf];B[hb];W[ad];B[jr];W[gs];B[hs];W[es];B[ds];W[fr];B[cc];W[bb];B[fq];W[es];B[ae];W[ac];B[ds];W[fr];B[lh];W[mh];B[fq];W[es];B[qo];W[ro];B[ds];W[fr];B[nj];W[mj];B[fq];W[es];B[bf];W[pi];B[pj];W[ri];B[qj];W[rh];B[gb];W[fb];B[ra];W[sb];B[le];W[me];B[md];W[rj];B[jf];W[sk];B[ks];W[fr];B[lf];W[if];B[id];W[ic];B[je];W[em];B[en];W[ck];B[ga];W[ek];B[dj];W[is];B[kq];W[gs];B[nm];W[nl];B[hk];W[im];B[jn];W[kn];B[gk];W[fk];B[db];W[da];B[fa];W[ea];B[jm];W[jl];B[ih];W[ig];B[jg];W[lg];B[kg];W[oe];B[od];W[dd];B[fj];W[ce];B[be];W[de];B[hh];W[jo];B[io];W[om];B[ap];W[aq];B[ao];W[qk];B[pn];W[am];B[bn];W[pp];B[rk];W[qe];B[pe];W[qk];B[sd];W[se];B[rk];W[qa];B[pa];W[qk];B[hl];W[hm];B[rk];W[jb];B[kb];W[qk];B[qs];W[rk];B[or];W[oh];B[ss];W[sq];B[ng];W[ik];B[hn];W[dm];B[cm];W[sd];B[qa];W[sa];B[kj];W[mk];B[ml];W[mi];B[ja];W[lj];B[dh];W[kk];B[ei];W[fc];B[bh];W[];B[cg];W[];B[no];W[];B[mp];W[];B[])"; + + // runBotOnSgf(bot, sgfStr, rules, 191, 7.5, opts); + // runBotOnSgf(bot, sgfStr, rules, 197, 7.5, opts); + // runBotOnSgf(bot, sgfStr, rules, 330, 7.5, opts); + // runBotOnSgf(bot, sgfStr, rules, 330, 7.0, opts); + + // cout << endl; + // cout << "Jigo and drawUtility===================" << endl; + // cout << "(Game almost over, just a little cleanup)" << endl; + // SearchParams testParams = params; + // testParams.drawEquivalentWinsForWhite = 0.7; + // cout << "testParams.drawEquivalentWinsForWhite = 0.7" << endl; + // cout << endl; + + // bot->setParams(testParams); + // cout << "Komi 7.5 (white wins by 0.5)" << endl; + // runBotOnSgf(bot, sgfStr, rules, 330, 7.5, opts); + // cout << endl; + + // cout << "Komi 7.0 (draw)" << endl; + // runBotOnSgf(bot, sgfStr, rules, 330, 7.0, opts); + // bot->setParams(params); + + // cout << endl; + // cout << "Consecutive searches playouts and visits===================" << endl; + // cout << "Doing three consecutive searches by visits" << endl; + // cout << endl; + // TestSearchOptions opts2 = opts; + // opts2.numMovesInARow = 3; + // opts2.printAfterBegun = true; + // runBotOnSgf(bot, sgfStr, rules, 85, 7.5, opts2); + // cout << endl; + + // cout << "Doing three consecutive searches by playouts (limit 200)" << endl; + // cout << endl; + // testParams = params; + // testParams.maxPlayouts = 200; + // testParams.maxVisits = 10000; + // bot->setParams(testParams); + // runBotOnSgf(bot, sgfStr, rules, 85, 7.5, opts2); + // bot->setParams(params); + // cout << endl << endl; + // } { cout << "GAME 4 ==========================================================================" << endl; diff --git a/cpp/tests/testsearchv3.cpp b/cpp/tests/testsearchv3.cpp index caeeda323..18ac90396 100644 --- a/cpp/tests/testsearchv3.cpp +++ b/cpp/tests/testsearchv3.cpp @@ -200,101 +200,101 @@ static void runOwnershipAndMisc(NNEvaluator* nnEval, NNEvaluator* nnEval11, NNEv delete bot; } - { - cout << "GAME 10 ==========================================================================" << endl; - cout << "(Tricky endgame seki invasion, testing LCB and dynamic utility recompute)" << endl; - cout << endl; - - SearchParams params; - params.maxVisits = 280; - params.staticScoreUtilityFactor = 0.2; - params.dynamicScoreUtilityFactor = 0.3; - params.useLcbForSelection = true; - AsyncBot* bot = new AsyncBot(params, nnEval, &logger, getSearchRandSeed()); - Rules rules = Rules::getTrompTaylorish(); - TestSearchOptions opts; - - - string sgfStr = "(;GM[1]FF[4]CA[UTF-8]SZ[19]HA[6]KM[0.5]AB[dc][oc][qd][ce][qo][pq];W[cp];B[ep];W[eq];B[fq];W[dq];B[fp];W[dn];B[jq];W[jp];B[ip];W[kq];B[iq];W[kp];B[fm];W[io];B[ho];W[in];B[en];W[dm];B[hn];W[oq];B[op];W[pr];B[pp];W[or];B[qr];W[mq];B[mo];W[qj];B[ql];W[qe];B[rd];W[qg];B[pe];W[ic];B[gc];W[lc];B[ch];W[cj];B[eh];W[ec];B[eb];W[dd];B[ed];W[cc];B[fc];W[db];B[cd];W[ec];B[de];W[dc];B[gb];W[ea];B[fb];W[bb];B[bd];W[ca];B[bc];W[ab];B[ee];W[nc];B[nd];W[ob];B[nb];W[mc];B[pb];W[od];B[pc];W[ne];B[md];W[le];B[oe];W[rl];B[rm];W[rk];B[qm];W[ie];B[me];W[mf];B[nf];W[ld];B[pd];W[ge];B[hd];W[he];B[fd];W[mg];B[id];W[jd];B[hh];W[bi];B[bh];W[ln];B[im];W[jm];B[jl];W[km];B[lo];W[ko];B[il];W[ek];B[dp];W[cq];B[do];W[co];B[fj];W[jh];B[ig];W[jg];B[nm];W[re];B[se];W[rf];B[pj];W[pi];B[oj];W[qk];B[oi];W[ph];B[mb];W[pk];B[ol];W[ok];B[nk];W[nj];B[mj];W[ni];B[mi];W[nh];B[mk];W[er];B[lb];W[kb];B[fr];W[fk];B[ff];W[di];B[ci];W[bj];B[ei];W[dj];B[dh];W[sf];B[jr];W[kr];B[sd];W[qs];B[rr];W[gl];B[gm];W[ib];B[ks];W[ls];B[js];W[np];B[no];W[pl];B[pm];W[if];B[mp];W[mr];B[nq];W[nr];B[gg];W[rs];B[og];W[oh];B[mn];W[ll];B[lh];W[ih];B[hg];W[ml];B[nl];W[gj];B[kl];W[lk];B[gi];W[ej];B[fi];W[hl];B[hj];W[lg];B[gk];W[fl];B[hk];W[em];B[hm];W[sm];B[sn];W[sl];B[sp];W[la];B[kj];W[pf];B[of];W[ii];B[lj];W[lm];B[kh];W[kg];B[fa];W[da];B[jj];W[fs];B[gs];W[es];B[ha];W[ia];B[ij];W[ah];B[ag];W[ai];B[pg];W[qf];B[lp];W[lq];B[hb];W[kk];B[jk];W[ac];B[ad];W[ji];B[ki];W[ka];B[oa];W[ma];B[na];W[sr];B[sq];W[ps];B[ss];W[np];B[sr];W[nq];B[mh];W[ng];B[fe];W[jn];B[mm];W[gr];B[hs];W[fn];B[eo];W[hr];B[is];W[gp];B[go];W[gq];B[hp];W[fo];B[])"; - - opts.noClearBot = true; - runBotOnSgf(bot, sgfStr, rules, 234, 0.5, opts); - - //Try to check that search tree is idempotent under simply rebeginning the search - Search* search = bot->getSearchStopAndWait(); - PrintTreeOptions options; - options = options.maxDepth(1); - cout << "Beginning search again and then reprinting, should be same" << endl; - search->beginSearch(false); - search->printTree(cout, search->rootNode, options, P_WHITE); - cout << "Making a move O3, should still be same" << endl; - bot->makeMove(Location::ofString("O3",19,19), P_WHITE); - search->printTree(cout, search->rootNode, options, P_WHITE); - cout << "Beginning search again and then reprinting, now score utils should change a little" << endl; - search->beginSearch(false); - search->printTree(cout, search->rootNode, options, P_WHITE); - - delete bot; - } - - - { - cout << "GAME 11 ==========================================================================" << endl; - cout << "(Non-square board)" << endl; - cout << endl; - - Rules rules = Rules::getTrompTaylorish(); - Player nextPla = P_BLACK; - Board boardA = Board::parseBoard(7,11,R"%%( -....... -....... -..x.o.. -....... -....... -...xo.. -....... -..xx... -..oox.. -....o.. -....... -)%%"); - BoardHistory histA(boardA,nextPla,rules,0); - - Board boardB = Board::parseBoard(11,7,R"%%( -........... -........... -..x.o.ox... -.......ox.. -.......ox.. -........... -........... -)%%"); - BoardHistory histB(boardB,nextPla,rules,0); - - SearchParams params; - params.maxVisits = 200; - params.dynamicScoreUtilityFactor = 0.25; - - AsyncBot* botA = new AsyncBot(params, nnEval, &logger, getSearchRandSeed()); - AsyncBot* botB = new AsyncBot(params, nnEval, &logger, getSearchRandSeed()); - - TestSearchOptions opts; - runBotOnPosition(botA,boardA,nextPla,histA,opts); - runBotOnPosition(botB,boardB,nextPla,histB,opts); - delete botA; - delete botB; - - cout << endl; - cout << "NNLen 11" << endl; - cout << endl; - AsyncBot* botA11 = new AsyncBot(params, nnEval11, &logger, getSearchRandSeed()); - AsyncBot* botB11 = new AsyncBot(params, nnEval11, &logger, getSearchRandSeed()); - runBotOnPosition(botA11,boardA,nextPla,histA,opts); - runBotOnPosition(botB11,boardB,nextPla,histB,opts); - - delete botA11; - delete botB11; - } + // { + // cout << "GAME 10 ==========================================================================" << endl; + // cout << "(Tricky endgame seki invasion, testing LCB and dynamic utility recompute)" << endl; + // cout << endl; + + // SearchParams params; + // params.maxVisits = 280; + // params.staticScoreUtilityFactor = 0.2; + // params.dynamicScoreUtilityFactor = 0.3; + // params.useLcbForSelection = true; + // AsyncBot* bot = new AsyncBot(params, nnEval, &logger, getSearchRandSeed()); + // Rules rules = Rules::getTrompTaylorish(); + // TestSearchOptions opts; + + + // string sgfStr = "(;GM[1]FF[4]CA[UTF-8]SZ[19]HA[6]KM[0.5]AB[dc][oc][qd][ce][qo][pq];W[cp];B[ep];W[eq];B[fq];W[dq];B[fp];W[dn];B[jq];W[jp];B[ip];W[kq];B[iq];W[kp];B[fm];W[io];B[ho];W[in];B[en];W[dm];B[hn];W[oq];B[op];W[pr];B[pp];W[or];B[qr];W[mq];B[mo];W[qj];B[ql];W[qe];B[rd];W[qg];B[pe];W[ic];B[gc];W[lc];B[ch];W[cj];B[eh];W[ec];B[eb];W[dd];B[ed];W[cc];B[fc];W[db];B[cd];W[ec];B[de];W[dc];B[gb];W[ea];B[fb];W[bb];B[bd];W[ca];B[bc];W[ab];B[ee];W[nc];B[nd];W[ob];B[nb];W[mc];B[pb];W[od];B[pc];W[ne];B[md];W[le];B[oe];W[rl];B[rm];W[rk];B[qm];W[ie];B[me];W[mf];B[nf];W[ld];B[pd];W[ge];B[hd];W[he];B[fd];W[mg];B[id];W[jd];B[hh];W[bi];B[bh];W[ln];B[im];W[jm];B[jl];W[km];B[lo];W[ko];B[il];W[ek];B[dp];W[cq];B[do];W[co];B[fj];W[jh];B[ig];W[jg];B[nm];W[re];B[se];W[rf];B[pj];W[pi];B[oj];W[qk];B[oi];W[ph];B[mb];W[pk];B[ol];W[ok];B[nk];W[nj];B[mj];W[ni];B[mi];W[nh];B[mk];W[er];B[lb];W[kb];B[fr];W[fk];B[ff];W[di];B[ci];W[bj];B[ei];W[dj];B[dh];W[sf];B[jr];W[kr];B[sd];W[qs];B[rr];W[gl];B[gm];W[ib];B[ks];W[ls];B[js];W[np];B[no];W[pl];B[pm];W[if];B[mp];W[mr];B[nq];W[nr];B[gg];W[rs];B[og];W[oh];B[mn];W[ll];B[lh];W[ih];B[hg];W[ml];B[nl];W[gj];B[kl];W[lk];B[gi];W[ej];B[fi];W[hl];B[hj];W[lg];B[gk];W[fl];B[hk];W[em];B[hm];W[sm];B[sn];W[sl];B[sp];W[la];B[kj];W[pf];B[of];W[ii];B[lj];W[lm];B[kh];W[kg];B[fa];W[da];B[jj];W[fs];B[gs];W[es];B[ha];W[ia];B[ij];W[ah];B[ag];W[ai];B[pg];W[qf];B[lp];W[lq];B[hb];W[kk];B[jk];W[ac];B[ad];W[ji];B[ki];W[ka];B[oa];W[ma];B[na];W[sr];B[sq];W[ps];B[ss];W[np];B[sr];W[nq];B[mh];W[ng];B[fe];W[jn];B[mm];W[gr];B[hs];W[fn];B[eo];W[hr];B[is];W[gp];B[go];W[gq];B[hp];W[fo];B[])"; + + // opts.noClearBot = true; + // runBotOnSgf(bot, sgfStr, rules, 234, 0.5, opts); + + // //Try to check that search tree is idempotent under simply rebeginning the search + // Search* search = bot->getSearchStopAndWait(); + // PrintTreeOptions options; + // options = options.maxDepth(1); + // cout << "Beginning search again and then reprinting, should be same" << endl; + // search->beginSearch(false); + // search->printTree(cout, search->rootNode, options, P_WHITE); + // cout << "Making a move O3, should still be same" << endl; + // bot->makeMove(Location::ofString("O3",19,19), P_WHITE); + // search->printTree(cout, search->rootNode, options, P_WHITE); + // cout << "Beginning search again and then reprinting, now score utils should change a little" << endl; + // search->beginSearch(false); + // search->printTree(cout, search->rootNode, options, P_WHITE); + + // delete bot; + // } + + +// { +// cout << "GAME 11 ==========================================================================" << endl; +// cout << "(Non-square board)" << endl; +// cout << endl; + +// Rules rules = Rules::getTrompTaylorish(); +// Player nextPla = P_BLACK; +// Board boardA = Board::parseBoard(7,11,R"%%( +// ....... +// ....... +// ..x.o.. +// ....... +// ....... +// ...xo.. +// ....... +// ..xx... +// ..oox.. +// ....o.. +// ....... +// )%%"); +// BoardHistory histA(boardA,nextPla,rules,0); + +// Board boardB = Board::parseBoard(11,7,R"%%( +// ........... +// ........... +// ..x.o.ox... +// .......ox.. +// .......ox.. +// ........... +// ........... +// )%%"); +// BoardHistory histB(boardB,nextPla,rules,0); + +// SearchParams params; +// params.maxVisits = 200; +// params.dynamicScoreUtilityFactor = 0.25; + +// AsyncBot* botA = new AsyncBot(params, nnEval, &logger, getSearchRandSeed()); +// AsyncBot* botB = new AsyncBot(params, nnEval, &logger, getSearchRandSeed()); + +// TestSearchOptions opts; +// runBotOnPosition(botA,boardA,nextPla,histA,opts); +// runBotOnPosition(botB,boardB,nextPla,histB,opts); +// delete botA; +// delete botB; + +// cout << endl; +// cout << "NNLen 11" << endl; +// cout << endl; +// AsyncBot* botA11 = new AsyncBot(params, nnEval11, &logger, getSearchRandSeed()); +// AsyncBot* botB11 = new AsyncBot(params, nnEval11, &logger, getSearchRandSeed()); +// runBotOnPosition(botA11,boardA,nextPla,histA,opts); +// runBotOnPosition(botB11,boardB,nextPla,histB,opts); + +// delete botA11; +// delete botB11; +// } { @@ -476,45 +476,45 @@ xx..xoooo delete bot; } - { - cout << "GAME 16 ==========================================================================" << endl; - cout << "Chinese rules endgame, one dame" << endl; - cout << endl; - - string seed = getSearchRandSeed(); - Rules rules = Rules::parseRules("Chinese"); - rules.komi = 6; - TestSearchOptions opts; - opts.noClearBot = true; - - Player nextPla = P_BLACK; - Board board = Board::parseBoard(9,7,R"%%( -......... -ooooo.o.. -oxxxox... -xx..xoooo -..xx.x.xo -.oox.xxxx -..x...ox. -)%%"); - board.numWhiteCaptures = 3; - BoardHistory hist(board,nextPla,rules,0); - - MiscNNInputParams nnInputParams; - NNResultBuf buf; - bool skipCache = true; - bool includeOwnerMap = true; - nnEval->evaluate(board,hist,nextPla,nnInputParams,buf,skipCache,includeOwnerMap); - printPolicyValueOwnership(board,buf); - - SearchParams params; - params.maxVisits = 200; - AsyncBot* bot = new AsyncBot(params, nnEval, &logger, seed); - - runBotOnPosition(bot,board,nextPla,hist,opts); - bot->getSearch()->printTree(cout, bot->getSearch()->rootNode, PrintTreeOptions().onlyBranch(board,"G3"), P_BLACK); - delete bot; - } +// { +// cout << "GAME 16 ==========================================================================" << endl; +// cout << "Chinese rules endgame, one dame" << endl; +// cout << endl; + +// string seed = getSearchRandSeed(); +// Rules rules = Rules::parseRules("Chinese"); +// rules.komi = 6; +// TestSearchOptions opts; +// opts.noClearBot = true; + +// Player nextPla = P_BLACK; +// Board board = Board::parseBoard(9,7,R"%%( +// ......... +// ooooo.o.. +// oxxxox... +// xx..xoooo +// ..xx.x.xo +// .oox.xxxx +// ..x...ox. +// )%%"); +// board.numWhiteCaptures = 3; +// BoardHistory hist(board,nextPla,rules,0); + +// MiscNNInputParams nnInputParams; +// NNResultBuf buf; +// bool skipCache = true; +// bool includeOwnerMap = true; +// nnEval->evaluate(board,hist,nextPla,nnInputParams,buf,skipCache,includeOwnerMap); +// printPolicyValueOwnership(board,buf); + +// SearchParams params; +// params.maxVisits = 200; +// AsyncBot* bot = new AsyncBot(params, nnEval, &logger, seed); + +// runBotOnPosition(bot,board,nextPla,hist,opts); +// bot->getSearch()->printTree(cout, bot->getSearch()->rootNode, PrintTreeOptions().onlyBranch(board,"G3"), P_BLACK); +// delete bot; +// } } void Tests::runSearchTestsV3(const string& modelFile, bool inputsNHWC, bool useNHWC, int symmetry, bool useFP16) { diff --git a/match.rb b/match.rb new file mode 100755 index 000000000..e166c9c32 --- /dev/null +++ b/match.rb @@ -0,0 +1,35 @@ +#!/usr/bin/env ruby + +pairs = %w[ + daoqi-s829795584-d13141612:daoqi-s961833984-d8749405 + daoqi-s829795584-d13141612:daoqi-s980315264-d10940344 + daoqi-s829795584-d13141612:daoqi-s991770368-d11953668 + daoqi-s961833984-d8749405:daoqi-s980315264-d10940344 + daoqi-s961833984-d8749405:daoqi-s991770368-d11953668 + daoqi-s980315264-d10940344:daoqi-s991770368-d11953668 +] + +pairs.each do |pair| + model0, model1 = pair.split ":" + f = File.open("/home/gcao/KataGo/cpp/configs/match_example2.cfg", "w") + File.foreach("/home/gcao/KataGo/cpp/configs/match_example2.cfg.bak") do |line| + if line =~ /^botName0 =/ + f.puts "botName0 = #{model0}" + elsif line =~ /^botName1 =/ + f.puts "botName1 = #{model1}" + elsif line =~ /^nnModelFile0 =/ + f.puts "nnModelFile0 = /home/gcao/daoqi-opencl-new/models/#{model0}/model.bin.gz" + elsif line =~ /^nnModelFile1 =/ + f.puts "nnModelFile1 = /home/gcao/daoqi-opencl-new/models/#{model1}/model.bin.gz" + else + f.puts line + end + end + f.close + + system "cpp/katago match -config cpp/configs/match_example2.cfg -sgf-output-dir /tmp/games" + s = `cat /tmp/games/* |cut -b -160 |grep -e "PB\\[#{model1}.*\\[B\\+" -e "PW\\[#{model1}.*\\[W\\+" |wc |awk '{print $1}'` + puts "#{model1} #{s.strip} : #{50 - s.strip.to_i} #{model0}" + puts `cat /tmp/games/* |cut -b -160 |grep -e "PB\\[#{model1}.*\\[B\\+" -e "PW\\[#{model1}.*\\[W\\+"` + `rm /tmp/games/*` +end diff --git a/python/board.py b/python/board.py index 4aa2e21ac..20496b7c5 100644 --- a/python/board.py +++ b/python/board.py @@ -84,7 +84,109 @@ def loc_x(self,loc): def loc_y(self,loc): return (loc // self.dy)-1 + def update_adj(self, loc): + x = self.loc_x(loc) + y = self.loc_y(loc) + size_1 = self.size - 1 + + if x == 0: + self.adj[1] = size_1 + self.adj[2] = 1 + elif x == size_1: + self.adj[1] = -1 + self.adj[2] = -size_1 + else: + self.adj[1] = -1 + self.adj[2] = 1 + + if y == 0: + self.adj[0] = self.dy * size_1 + self.adj[3] = self.dy + elif y == size_1: + self.adj[0] = -self.dy + self.adj[3] = -self.dy * size_1 + else: + self.adj[0] = -self.dy + self.adj[3] = self.dy + + return self.adj + + def update_diag(self, loc): + x = self.loc_x(loc) + y = self.loc_y(loc) + size_1 = self.size - 1 + + if x == 0: + if y == 0: + self.diag = [ + size_1 * self.dy + size_1, + size_1 * self.dy + 1, + self.dy + size_1, + self.dy + 1, + ] + elif y == size_1: + self.diag = [ + size_1 * self.dy - 1, + size_1 * self.dy - size_1, + self.dy - 1, + self.dy - size_1, + ] + else: + self.diag = [ + size_1 * self.dy - 1, + size_1 * self.dy + 1, + self.dy - 1, + self.dy + 1, + ] + elif x == size_1: + if y == 0: + self.diag = [ + -self.dy + size_1, + -self.dy + 1, + -size_1 * self.dy + size_1, + -size_1 * self.dy + 1, + ] + elif y == size_1: + self.diag = [ + -self.dy - 1, + -self.dy - size_1, + -size_1 * self.dy - 1, + -size_1 * self.dy - size_1, + ] + else: + self.diag = [ + -self.dy - 1, + -self.dy + 1, + -size_1 * self.dy - 1, + -size_1 * self.dy + 1, + ] + else: + if y == 0: + self.diag = [ + -self.dy + size_1, + -self.dy + 1, + self.dy + size_1, + self.dy + 1, + ] + elif y == size_1: + self.diag = [ + -self.dy - 1, + -self.dy - size_1, + self.dy - 1, + self.dy - size_1, + ] + else: + self.diag = [ + -self.dy - 1, + -self.dy + 1, + self.dy - 1, + self.dy + 1 + ] + + return self.diag + def is_adjacent(self,loc1,loc2): + self.update_adj(loc2) return loc1 == loc2 + self.adj[0] or loc1 == loc2 + self.adj[1] or loc1 == loc2 + self.adj[2] or loc1 == loc2 + self.adj[3] def pos_zobrist(self): @@ -98,6 +200,7 @@ def num_liberties(self,loc): return self.group_liberty_count[self.group_head[loc]] def is_simple_eye(self,pla,loc): + self.update_adj(loc) adj0 = loc + self.adj[0] adj1 = loc + self.adj[1] adj2 = loc + self.adj[2] @@ -111,6 +214,7 @@ def is_simple_eye(self,pla,loc): opp = Board.get_opp(pla) opp_corners = 0 + self.update_diag(loc) diag0 = loc + self.diag[0] diag1 = loc + self.diag[1] diag2 = loc + self.diag[2] @@ -157,6 +261,7 @@ def would_be_legal(self,pla,loc): return True def would_be_suicide(self,pla,loc): + self.update_adj(loc) adj0 = loc + self.adj[0] adj1 = loc + self.adj[1] adj2 = loc + self.adj[2] @@ -179,6 +284,7 @@ def would_be_suicide(self,pla,loc): return True def would_be_single_stone_suicide(self,pla,loc): + self.update_adj(loc) adj0 = loc + self.adj[0] adj1 = loc + self.adj[1] adj2 = loc + self.adj[2] @@ -206,6 +312,8 @@ def get_liberties_after_play(self,pla,loc,maxLibs): libs = [] capturedGroupHeads = [] + self.update_adj(loc) + #First, count immediate liberties and groups that would be captured for i in range(4): adj = loc + self.adj[i] @@ -233,6 +341,7 @@ def wouldBeEmpty(possibleLib): #Next, walk through all stones of all surrounding groups we would connect with and count liberties, avoiding overlap. connectingGroupHeads = [] for i in range(4): + self.update_adj(loc) adj = loc + self.adj[i] if self.board[adj] == pla: head = self.group_head[adj] @@ -241,6 +350,7 @@ def wouldBeEmpty(possibleLib): cur = adj while True: + self.update_adj(cur) for k in range(4): possibleLib = cur + self.adj[k] if possibleLib != loc and wouldBeEmpty(possibleLib) and possibleLib not in libs: @@ -341,6 +451,7 @@ def playRecordedUnsafe(self,pla,loc): capDirs = [] opp = Board.get_opp(pla) old_simple_ko_point = self.simple_ko_point + self.update_adj(loc) for i in range(4): adj = loc + self.adj[i] if self.board[adj] == opp and self.group_liberty_count[self.group_head[adj]] == 1: @@ -366,6 +477,7 @@ def undo(self,record): #Re-fill stones in all captured directions for capdir in capDirs: + self.update_adj(loc) adj = loc + self.adj[capdir] if self.board[adj] == Board.EMPTY: self.floodFillStones(opp,adj) @@ -398,6 +510,7 @@ def undo(self,record): #Rebuild each chain adjacent now for i in range(4): + self.update_adj(loc) adj = loc + self.adj[i] if self.board[adj] == pla and self.group_head[adj] == Board.PASS_LOC: self.rebuildChain(pla,adj) @@ -440,6 +553,7 @@ def floodFillStonesHelper(self, head, tailTarget, loc, pla): #Recursively add stones around us. nextTailTarget = loc for i in range(4): + self.update_adj(loc) adj = loc + self.adj[i] if self.board[adj] == Board.EMPTY: nextTailTarget = self.floodFillStonesHelper(head,nextTailTarget,adj,pla) @@ -468,7 +582,9 @@ def rebuildChain(self,pla,loc): #some invalid location, such as NULL_LOC or a location not of color. def rebuildChainHelper(self, head, tailTarget, loc, pla): #Count new liberties - for dloc in self.adj: + for i in range(4): + self.update_adj(loc) + dloc = self.adj[i] if self.board[loc+dloc] == Board.EMPTY and not self.is_group_adjacent(head,loc+dloc): self.group_liberty_count[head] += 1 @@ -481,6 +597,7 @@ def rebuildChainHelper(self, head, tailTarget, loc, pla): #Recursively add stones around us. nextTailTarget = loc for i in range(4): + self.update_adj(loc) adj = loc + self.adj[i] if self.board[adj] == pla and self.group_head[adj] != head: nextTailTarget = self.rebuildChainHelper(head,nextTailTarget,adj,pla) @@ -499,6 +616,7 @@ def add_unsafe(self,pla,loc): self.group_head[loc] = loc self.group_stone_count[loc] = 1 liberties = 0 + self.update_adj(loc) for dloc in self.adj: if self.board[loc+dloc] == Board.EMPTY: liberties += 1 @@ -571,6 +689,7 @@ def add_unsafe(self,pla,loc): #Apply the specified delta to the liberties of all adjacent groups of the specified color def changeSurroundingLiberties(self,loc,pla,delta): + self.update_adj(loc) #Carefully avoid doublecounting adj0 = loc + self.adj[0] adj1 = loc + self.adj[1] @@ -592,6 +711,7 @@ def changeSurroundingLiberties(self,loc,pla,delta): self.group_liberty_count[self.group_head[adj3]] += delta def countImmediateLiberties(self,loc): + self.update_adj(loc) adj0 = loc + self.adj[0] adj1 = loc + self.adj[1] adj2 = loc + self.adj[2] @@ -608,6 +728,7 @@ def countImmediateLiberties(self,loc): return count def is_group_adjacent(self,head,loc): + self.update_adj(loc) return ( self.group_head[loc+self.adj[0]] == head or \ self.group_head[loc+self.adj[1]] == head or \ @@ -634,6 +755,7 @@ def merge_unsafe(self,loc0,loc1): new_liberties = self.group_liberty_count[phead] loc = child while True: + self.update_adj(loc) adj0 = loc + self.adj[0] adj1 = loc + self.adj[1] adj2 = loc + self.adj[2] @@ -684,6 +806,7 @@ def remove_unsafe(self,group): loc = group while True: #Add a liberty to all surrounding opposing groups, taking care to avoid double counting + self.update_adj(loc) adj0 = loc + self.adj[0] adj1 = loc + self.adj[1] adj2 = loc + self.adj[2] @@ -747,6 +870,7 @@ def remove_single_stone_unsafe(self,rloc): def findLiberties(self, loc, buf): cur = loc while True: + self.update_adj(loc) for i in range(4): lib = cur + self.adj[i] if self.board[lib] == Board.EMPTY: @@ -789,6 +913,7 @@ def hasLibertyGainingCaptures(self, loc): cur = loc while True: + self.update_adj(loc) for i in range(4): adj = cur + self.adj[i] if self.board[adj] == opp: @@ -808,6 +933,7 @@ def wouldBeKoCapture(self, loc, pla): #Check that surounding points are are all opponent owned and exactly one of them is capturable opp = Board.get_opp(pla) oppCapturableLoc = None + self.update_adj(loc) for i in range(4): adj = loc + self.adj[i] if self.board[adj] != Board.WALL and self.board[adj] != opp: @@ -826,6 +952,7 @@ def wouldBeKoCapture(self, loc, pla): return True def countHeuristicConnectionLiberties(self,loc,pla): + self.update_adj(loc) adj0 = loc + self.adj[0] adj1 = loc + self.adj[1] adj2 = loc + self.adj[2] @@ -1159,6 +1286,7 @@ def calculateAreaForPla(self, pla, safeBigTerritories, unsafeBigTerritories, isM containsOpp = [False for i in range(maxRegions)] def isAdjacentToPlaHead(loc,plaHead): + self.update_adj(loc) for i in range(4): adj = loc + self.adj[i] if self.board[adj] == pla and self.group_head[adj] == plaHead: @@ -1176,6 +1304,8 @@ def buildRegion(head, tailTarget, loc, regionIdx): return tailTarget regionHeadByLoc[loc] = head + self.update_adj(loc) + #First, filter out any pla heads it turns out we're not vital for because we're not adjacent to them #In the case where suicide is allowed, we only do this filtering on intersections that are actually empty if isMultiStoneSuicideLegal or self.board[loc] == Board.EMPTY: @@ -1206,6 +1336,7 @@ def buildRegion(head, tailTarget, loc, regionIdx): nextEmptyOrOpp[loc] = tailTarget nextTailTarget = loc for i in range(4): + self.update_adj(loc) adj = loc + self.adj[i] if self.board[adj] == Board.EMPTY or self.board[adj] == opp: nextTailTarget = buildRegion(head,nextTailTarget,adj,regionIdx) @@ -1239,6 +1370,7 @@ def buildRegion(head, tailTarget, loc, regionIdx): vStart = vitalStart[regionIdx] assert(vStart + 4 <= vitalForPlaHeadsListsMaxLen) initialVLen = 0 + self.update_adj(loc) for i in range(4): adj = loc + self.adj[i] if self.board[adj] == pla: @@ -1311,6 +1443,7 @@ def buildRegion(head, tailTarget, loc, regionIdx): #Walk the pla chain to update bordering regions cur = plaHead while(True): + self.update_adj(loc) for j in range(4): adj = cur + self.adj[j] if self.board[adj] == Board.EMPTY or self.board[adj] == opp: @@ -1368,6 +1501,11 @@ def calculateNonDameTouchingAreaHelper(self, basicArea, result): for y in range(self.size): for x in range(self.size): loc = self.loc(x,y) + self.update_adj(loc) + ADJ0 = self.adj[0] + ADJ1 = self.adj[1] + ADJ2 = self.adj[2] + ADJ3 = self.adj[3] if basicArea[loc] != Board.EMPTY and not isDameTouching[loc]: #Touches dame? if((self.board[loc+ADJ0] == Board.EMPTY and basicArea[loc+ADJ0] == Board.EMPTY) or @@ -1410,6 +1548,8 @@ def calculateNonDameTouchingAreaHelper(self, basicArea, result): nextLoc = queue[queueHead] queueHead += 1 + self.update_adj(nextLoc) + #Look all around it, floodfill for j in range(4): adj = nextLoc + self.adj[j] diff --git a/python/model.py b/python/model.py index e9b2a092d..92b673851 100644 --- a/python/model.py +++ b/python/model.py @@ -604,11 +604,43 @@ def weight_variable(self, name, shape, num_inputs, num_outputs, scale_initial_we self.reg_variables_tiny.append(variable) return variable - def conv2d(self, x, w): - return tf.nn.conv2d(x, w, strides=[1,1,1,1], padding='SAME') + def conv2d(self, x, w, diam): + padding = 'SAME' + radius = diam//2 - def dilated_conv2d(self, x, w, dilation): - return tf.nn.atrous_conv2d(x, w, rate = dilation, padding='SAME') + if radius > 0: + padding = 'VALID' + + # Cyclic update for y axis + x1 = x[:, :, -1-radius:-1, :] + x2 = x[:, :, 0:radius, :] + x = tf.concat([x1, x, x2], 2) + + # Cyclic update for x axis + x1 = x[:, -1-radius:-1, :, :] + x2 = x[:, 0:radius, :, :] + x = tf.concat([x1, x, x2], 1) + + return tf.nn.conv2d(x, w, strides=[1,1,1,1], padding=padding) + + def dilated_conv2d(self, x, w, diam, dilation): + padding = 'SAME' + radius = diam//2 + + if radius > 0: + padding = 'VALID' + + # Cyclic update for y axis + x1 = x[:, :, -1-radius:-1, :] + x2 = x[:, :, 0:radius, :] + x = tf.concat([x1, x, x2], 2) + + # Cyclic update for x axis + x1 = x[:, -1-radius:-1, :, :] + x2 = x[:, 0:radius, :, :] + x = tf.concat([x1, x, x2], 1) + + return tf.nn.atrous_conv2d(x, w, rate = dilation, padding=padding) def apply_symmetry(self,tensor,symmetries,inverse): ud = symmetries[0] @@ -684,7 +716,7 @@ def conv_block( scale_initial_weights=1.0 ): weights = self.conv_weight_variable(name+"/w", diam, diam, in_channels, out_channels, scale_initial_weights) - convolved = self.conv2d(in_layer, weights) + convolved = self.conv2d(in_layer, weights, diam) self.outputs_by_layer.append((name+"/prenorm",convolved)) out_layer = self.relu(name+"/relu",self.batchnorm_and_mask(name+"/norm",convolved,mask,mask_sum)) self.outputs_by_layer.append((name,out_layer)) @@ -696,7 +728,7 @@ def conv_only_block( scale_initial_weights=1.0, reg=True ): weights = self.conv_weight_variable(name+"/w", diam, diam, in_channels, out_channels, scale_initial_weights, reg=reg) - out_layer = self.conv2d(in_layer, weights) + out_layer = self.conv2d(in_layer, weights, diam) self.outputs_by_layer.append((name,out_layer)) return out_layer @@ -710,7 +742,7 @@ def res_conv_block( fixup_scale = 1.0 / math.sqrt(self.num_blocks) if self.use_fixup else 1.0 weights1 = self.conv_weight_variable(name+"/w1", diam, diam, main_channels, mid_channels, scale_initial_weights * fixup_scale) - conv1_layer = self.conv2d(trans1_layer, weights1) + conv1_layer = self.conv2d(trans1_layer, weights1, diam) self.outputs_by_layer.append((name+"/conv1",conv1_layer)) trans2_layer = self.relu(name+"/relu2",(self.batchnorm_and_mask(name+"/norm2",conv1_layer,mask,mask_sum,use_gamma_in_fixup=True))) @@ -718,7 +750,7 @@ def res_conv_block( fixup_scale_last_layer = 0.0 if self.use_fixup else 1.0 weights2 = self.conv_weight_variable(name+"/w2", diam, diam, mid_channels, main_channels, scale_initial_weights*fixup_scale_last_layer) - conv2_layer = self.conv2d(trans2_layer, weights2) + conv2_layer = self.conv2d(trans2_layer, weights2, diam) self.outputs_by_layer.append((name+"/conv2",conv2_layer)) return conv2_layer @@ -736,8 +768,8 @@ def global_res_conv_block( fixup_scale4 = 1.0 / (self.num_blocks ** (1.0 / 4.0)) if self.use_fixup else 1.0 weights1a = self.conv_weight_variable(name+"/w1a", diam, diam, main_channels, mid_channels, scale_initial_weights * fixup_scale2) weights1b = self.conv_weight_variable(name+"/w1b", diam, diam, main_channels, global_mid_channels, scale_initial_weights * fixup_scale4) - conv1a_layer = self.conv2d(trans1_layer, weights1a) - conv1b_layer = self.conv2d(trans1_layer, weights1b) + conv1a_layer = self.conv2d(trans1_layer, weights1a, diam) + conv1b_layer = self.conv2d(trans1_layer, weights1b, diam) self.outputs_by_layer.append((name+"/conv1a",conv1a_layer)) self.outputs_by_layer.append((name+"/conv1b",conv1b_layer)) @@ -752,7 +784,7 @@ def global_res_conv_block( fixup_scale_last_layer = 0.0 if self.use_fixup else 1.0 weights2 = self.conv_weight_variable(name+"/w2", diam, diam, mid_channels, main_channels, scale_initial_weights * fixup_scale_last_layer) - conv2_layer = self.conv2d(trans2_layer, weights2) + conv2_layer = self.conv2d(trans2_layer, weights2, diam) self.outputs_by_layer.append((name+"/conv2",conv2_layer)) return conv2_layer @@ -765,8 +797,8 @@ def dilated_res_conv_block(self, name, in_layer, mask, mask_sum, diam, main_chan fixup_scale = 1.0 / math.sqrt(self.num_blocks) if self.use_fixup else 1.0 weights1a = self.conv_weight_variable(name+"/w1a", diam, diam, main_channels, mid_channels, scale_initial_weights*fixup_scale) weights1b = self.conv_weight_variable(name+"/w1b", diam, diam, main_channels, dilated_mid_channels, scale_initial_weights*fixup_scale) - conv1a_layer = self.conv2d(trans1_layer, weights1a) - conv1b_layer = self.dilated_conv2d(trans1_layer, weights1b, dilation=dilation) + conv1a_layer = self.conv2d(trans1_layer, weights1a, diam) + conv1b_layer = self.dilated_conv2d(trans1_layer, weights1b, diam, dilation=dilation) self.outputs_by_layer.append((name+"/conv1a",conv1a_layer)) self.outputs_by_layer.append((name+"/conv1b",conv1b_layer)) @@ -777,7 +809,7 @@ def dilated_res_conv_block(self, name, in_layer, mask, mask_sum, diam, main_chan fixup_scale_last_layer = 0.0 if self.use_fixup else 1.0 weights2 = self.conv_weight_variable(name+"/w2", diam, diam, mid_channels+dilated_mid_channels, main_channels, scale_initial_weights * fixup_scale_last_layer) - conv2_layer = self.conv2d(trans2_layer, weights2) + conv2_layer = self.conv2d(trans2_layer, weights2, diam) self.outputs_by_layer.append((name+"/conv2",conv2_layer)) return conv2_layer diff --git a/python/selfplay/synchronous_loop.sh b/python/selfplay/synchronous_loop.sh index 02b75fdd2..daaafe43c 100755 --- a/python/selfplay/synchronous_loop.sh +++ b/python/selfplay/synchronous_loop.sh @@ -45,12 +45,13 @@ mkdir -p "$BASEDIR"/gatekeepersgf # NOTE: You probably want to edit settings in the cpp/configs/selfplay1.cfg - what board sizes and rules, you want to learn, number of visits to use, etc. # NOTE: You may want to adjust these numbers. -NUM_GAMES_PER_CYCLE=1000 +# NUM_GAMES_PER_CYCLE is set below so that we don't have to restart the training in order to adjust this. +# NUM_GAMES_PER_CYCLE=1000 NUM_THREADS_FOR_SHUFFLING=8 -NUM_TRAIN_SAMPLES_PER_CYCLE=500000 +NUM_TRAIN_SAMPLES_PER_CYCLE=600000 BATCHSIZE=128 # KataGo normally uses batch size 256, and you can do that too, but for lower-end GPUs 64 or 128 may be needed to avoid running out of memory. -SHUFFLE_MINROWS=80000 -SHUFFLE_KEEPROWS=600000 # A little larger than NUM_TRAIN_SAMPLES_PER_CYCLE +SHUFFLE_MINROWS=10000 +SHUFFLE_KEEPROWS=650000 # A little larger than NUM_TRAIN_SAMPLES_PER_CYCLE # For archival and logging purposes - you can look back and see exactly the python code on a particular date DATE_FOR_FILENAME=$(date "+%Y%m%d-%H%M%S") @@ -61,9 +62,19 @@ cp "$GITROOTDIR"/python/*.py "$GITROOTDIR"/python/selfplay/*.sh "$DATED_ARCHIVE" set -x while true do + echo ---- ---- ---- ---- ---- + echo "Starting at" $(date "+%Y%m%d-%H%M%S") + echo ---- ---- ---- ---- ---- + echo + echo "Selfplay" + NUM_GAMES_PER_CYCLE=$(awk '/numGamesPerCycle/{print $NF}' "$GITROOTDIR"/cpp/configs/selfplay1.cfg) + echo NUM_GAMES_PER_CYCLE=$NUM_GAMES_PER_CYCLE time "$GITROOTDIR"/cpp/katago selfplay -max-games-total "$NUM_GAMES_PER_CYCLE" -output-dir "$BASEDIR"/selfplay -models-dir "$BASEDIR"/models -config "$GITROOTDIR"/cpp/configs/selfplay1.cfg | tee -a "$BASEDIR"/selfplay/stdout.txt + echo "Sleep 10 minutes to cool down CPU & GPU etc" + sleep 600 + echo "Shuffle" ( cd "$GITROOTDIR"/python @@ -81,6 +92,10 @@ do echo "Gatekeeper" time "$GITROOTDIR"/cpp/katago gatekeeper -rejected-models-dir "$BASEDIR"/rejectedmodels -accepted-models-dir "$BASEDIR"/models/ -sgf-output-dir "$BASEDIR"/gatekeepersgf/ -test-models-dir "$BASEDIR"/modelstobetested/ -config "$GITROOTDIR"/cpp/configs/gatekeeper1.cfg -quit-if-no-nets-to-test | tee -a "$BASEDIR"/gatekeepersgf/stdout.txt + + echo "Sleep 5 minutes to cool down CPU & GPU etc" + sleep 300 + done exit 0