3131#include " search/searcher.h"
3232#include " search/searchthread.h"
3333
34+ #include < algorithm>
3435#include < array>
3536#include < cpptoml.h>
3637#include < cstdint>
@@ -58,20 +59,60 @@ constexpr uint32_t I(uint32_t y, uint32_t x)
5859constexpr std::array<char , 8 > ClassicalModelExtensionMagic =
5960 {' R' , ' F' , ' C' , ' L' , ' M' , ' O' , ' D' , ' 1' };
6061constexpr uint32_t ClassicalModelCompactP3Version = 7 ;
62+ constexpr uint32_t ClassicalModelThreatGamVersion = 8 ;
6163constexpr uint32_t ModelPolicyTableCount = 1 ;
6264constexpr uint32_t ModelPolicyContextCount = Evaluation::PolicyStoredContextCount;
6365constexpr uint32_t ModelBlendComponentCount = 0 ;
6466constexpr uint32_t PolicyCrossPatternCount = Evaluation::PolicyStoredPatternCount;
6567constexpr uint32_t PolicyCrossPayloadBytes =
6668 ModelPolicyContextCount * PolicyCrossPatternCount * PolicyCrossPatternCount * sizeof (Score);
6769constexpr uint32_t ClassicalModelCompactP3PayloadBytes = PolicyCrossPayloadBytes;
70+ constexpr uint32_t ThreatGamExtraPayloadBytes =
71+ (RULE_NB + 1 )
72+ * (ThreatGamAxisCount * ThreatGamLevelCount
73+ + ThreatGamPairCount * ThreatGamLevelCount * ThreatGamLevelCount)
74+ * sizeof (Eval);
6875
6976static_assert (ModelPolicyContextCount == Search::QUIET );
7077static_assert (Search::QUIET + 1 == Search::POLICY_CONTEXT_NB );
7178static_assert (ModelPolicyContextCount < Evaluation::PolicyContextCount);
7279
7380using StoredPolicyCross =
7481 Score[ModelPolicyContextCount][PolicyCrossPatternCount][PolicyCrossPatternCount];
82+ using StoredThreatGamCount = Eval[RULE_NB + 1 ][ThreatGamAxisCount][ThreatGamLevelCount];
83+ using StoredThreatGamPair =
84+ Eval[RULE_NB + 1 ][ThreatGamPairCount][ThreatGamLevelCount][ThreatGamLevelCount];
85+
86+ bool hasValidThreatGamReferences (const StoredThreatGamCount &count, const StoredThreatGamPair &pair)
87+ {
88+ for (size_t table = 0 ; table < RULE_NB + 1 ; table++) {
89+ for (size_t axis = 0 ; axis < ThreatGamAxisCount; axis++)
90+ if (count[table][axis][0 ] != 0 || count[table][axis][1 ] != 0 )
91+ return false ;
92+ for (size_t p = 0 ; p < ThreatGamPairCount; p++)
93+ for (size_t lhs = 0 ; lhs < ThreatGamLevelCount; lhs++)
94+ for (size_t rhs = 0 ; rhs < ThreatGamLevelCount; rhs++)
95+ if ((lhs < 2 || rhs < 2 ) && pair[table][p][lhs][rhs] != 0 )
96+ return false ;
97+ }
98+ return true ;
99+ }
100+
101+ bool hasThreatGamValues (const StoredThreatGamCount &count, const StoredThreatGamPair &pair)
102+ {
103+ for (const auto &table : count)
104+ for (const auto &axis : table)
105+ for (Eval value : axis)
106+ if (value != 0 )
107+ return true ;
108+ for (const auto &table : pair)
109+ for (const auto &p : table)
110+ for (const auto &row : p)
111+ for (Eval value : row)
112+ if (value != 0 )
113+ return true ;
114+ return false ;
115+ }
75116
76117} // namespace
77118
@@ -84,13 +125,38 @@ float ScalingFactor = 200.0f;
84125// Classical evaluation and score tables
85126// Note that Renju has asymmetry eval and score
86127
87- Eval EVALS [RULE_NB + 1 ][PCODE_NB ];
88- Eval EVALS_THREAT [RULE_NB + 1 ][THREAT_NB ];
128+ Eval EVALS [RULE_NB + 1 ][PCODE_NB ];
129+ Eval EVALS_THREAT [RULE_NB + 1 ][THREAT_NB ];
130+ Eval THREAT_GAM_COUNT [RULE_NB + 1 ][ThreatGamAxisCount][ThreatGamLevelCount] = {};
131+ Eval THREAT_GAM_PAIR [RULE_NB + 1 ][ThreatGamPairCount][ThreatGamLevelCount][ThreatGamLevelCount] =
132+ {};
133+ bool THREAT_GAM_ACTIVE [RULE_NB + 1 ] = {};
89134MoveScorePair P4SCORES [RULE_NB + 1 ][PCODE_NB ];
90135Score POLICY_CROSS [RULE_NB + 1 ][PolicyContextCount][PATTERN4_NB ][PATTERN4_NB ];
91136uint8_t POLICY_CROSS_ACTIVE_MASK [RULE_NB + 1 ] = {};
92137bool POLICY_CROSS_PRESENT = false ;
93138
139+ void resetThreatGam ()
140+ {
141+ std::memset (THREAT_GAM_COUNT , 0 , sizeof (THREAT_GAM_COUNT ));
142+ std::memset (THREAT_GAM_PAIR , 0 , sizeof (THREAT_GAM_PAIR ));
143+ std::memset (THREAT_GAM_ACTIVE , 0 , sizeof (THREAT_GAM_ACTIVE ));
144+ }
145+
146+ void refreshThreatGamActivation ()
147+ {
148+ for (size_t table = 0 ; table < RULE_NB + 1 ; table++) {
149+ THREAT_GAM_ACTIVE [table] = false ;
150+ for (const auto &axis : THREAT_GAM_COUNT [table])
151+ for (Eval value : axis)
152+ THREAT_GAM_ACTIVE [table] |= value != 0 ;
153+ for (const auto &pair : THREAT_GAM_PAIR [table])
154+ for (const auto &row : pair)
155+ for (Eval value : row)
156+ THREAT_GAM_ACTIVE [table] |= value != 0 ;
157+ }
158+ }
159+
94160void resetPolicyCross ()
95161{
96162 std::memset (POLICY_CROSS , 0 , sizeof (POLICY_CROSS ));
@@ -270,7 +336,7 @@ void Config::readRequirement(const cpptoml::table &t)
270336{
271337 auto [major, minor, revision] = getVersionNumbers ();
272338 uint64_t rapVer = ((uint64_t )major << 32 ) | ((uint64_t )minor << 16 ) | (uint64_t )revision;
273- auto composeVersion = [](const std::vector<int64_t > &ver, const char *key) {
339+ auto composeVersion = [](const std::vector<int64_t > &ver, const char *key) {
274340 if (ver.size () != 3 )
275341 throw std::runtime_error (std::string (" illegal " ) + key);
276342 for (int64_t component : ver)
@@ -480,6 +546,7 @@ void Config::readModel(const cpptoml::table &t, PendingConfig &pending)
480546 // extensions. Do not let rows from a previously loaded binary model leak
481547 // across an in-process config reload.
482548 Evaluation::resetPolicyCross ();
549+ Evaluation::resetThreatGam ();
483550 std::memset (Evaluation::EVALS_THREAT , 0 , sizeof (Evaluation::EVALS_THREAT ));
484551
485552 // Read Eval & Score
@@ -854,22 +921,35 @@ bool Config::loadModel(std::istream &inStream)
854921 // Decode the whole payload into local staging buffers and publish to the
855922 // live tables only after every check has passed, so a truncated or corrupt
856923 // model file leaves the current evaluation state untouched.
857- double scalingFactorF64;
858- Eval evals[RULE_NB + 1 ][PCODE_NB ];
859- Eval evalsThreat[RULE_NB + 1 ][THREAT_NB ];
860- Score scores[RULE_NB + 1 ][PCODE_NB ][2 ];
924+ double scalingFactorF64;
925+ Eval evals[RULE_NB + 1 ][PCODE_NB ];
926+ Eval evalsThreat[RULE_NB + 1 ][THREAT_NB ];
927+ Score scores[RULE_NB + 1 ][PCODE_NB ][2 ];
928+ StoredThreatGamCount threatGamCount {};
929+ StoredThreatGamPair threatGamPair {};
861930 in->read (reinterpret_cast <char *>(&scalingFactorF64), sizeof (scalingFactorF64));
862931 in->read (reinterpret_cast <char *>(evals), sizeof (evals));
863932 in->read (reinterpret_cast <char *>(evalsThreat), sizeof (evalsThreat));
864933 in->read (reinterpret_cast <char *>(scores), sizeof (scores));
865934
866935 if (!*in)
867936 return false ;
937+ float scalingFactor = static_cast <float >(scalingFactorF64);
938+ if (!std::isfinite (scalingFactorF64) || scalingFactorF64 <= 0.0 || !std::isfinite (scalingFactor)
939+ || scalingFactor <= 0 .0f )
940+ return false ;
868941
869942 auto publishBaseTables = [&]() {
870- Evaluation::ScalingFactor = scalingFactorF64 ;
943+ Evaluation::ScalingFactor = scalingFactor ;
871944 std::memcpy (Evaluation::EVALS , evals, sizeof (Evaluation::EVALS ));
872945 std::memcpy (Evaluation::EVALS_THREAT , evalsThreat, sizeof (Evaluation::EVALS_THREAT ));
946+ std::memcpy (Evaluation::THREAT_GAM_COUNT ,
947+ threatGamCount,
948+ sizeof (Evaluation::THREAT_GAM_COUNT ));
949+ std::memcpy (Evaluation::THREAT_GAM_PAIR ,
950+ threatGamPair,
951+ sizeof (Evaluation::THREAT_GAM_PAIR ));
952+ Evaluation::refreshThreatGamActivation ();
873953
874954 // Set score table to P4SCORES
875955 for (int rule = 0 ; rule < RULE_NB + 1 ; rule++)
@@ -898,29 +978,56 @@ bool Config::loadModel(std::istream &inStream)
898978 in->read (reinterpret_cast <char *>(&contextCount), sizeof (contextCount));
899979 in->read (reinterpret_cast <char *>(&componentCount), sizeof (componentCount));
900980 in->read (reinterpret_cast <char *>(&patternCount), sizeof (patternCount));
901- if (!*in || magic != ClassicalModelExtensionMagic || version != ClassicalModelCompactP3Version
902- || payloadBytes != ClassicalModelCompactP3PayloadBytes
903- || tableCount != ModelPolicyTableCount || contextCount != ModelPolicyContextCount
904- || componentCount != ModelBlendComponentCount || patternCount != PolicyCrossPatternCount)
981+ if (!*in || magic != ClassicalModelExtensionMagic)
905982 return false ;
906983
907- StoredPolicyCross policyCross;
908- in->read (reinterpret_cast <char *>(policyCross), sizeof (policyCross));
909- if (!*in)
984+ bool hasPolicyCross = false ;
985+ StoredPolicyCross policyCross {};
986+ if (version == ClassicalModelCompactP3Version) {
987+ if (payloadBytes != ClassicalModelCompactP3PayloadBytes
988+ || tableCount != ModelPolicyTableCount || contextCount != ModelPolicyContextCount
989+ || componentCount != ModelBlendComponentCount
990+ || patternCount != PolicyCrossPatternCount)
991+ return false ;
992+ in->read (reinterpret_cast <char *>(policyCross), sizeof (policyCross));
993+ hasPolicyCross = true ;
994+ }
995+ else if (version == ClassicalModelThreatGamVersion) {
996+ bool validPolicyHeader = tableCount == 0 ? contextCount == 0 && patternCount == 0
997+ : tableCount == ModelPolicyTableCount
998+ && contextCount == ModelPolicyContextCount
999+ && patternCount == PolicyCrossPatternCount;
1000+ uint32_t expectedPayloadBytes =
1001+ ThreatGamExtraPayloadBytes + (tableCount ? PolicyCrossPayloadBytes : 0 );
1002+ if (!validPolicyHeader || componentCount != ModelBlendComponentCount
1003+ || payloadBytes != expectedPayloadBytes)
1004+ return false ;
1005+ if (tableCount) {
1006+ in->read (reinterpret_cast <char *>(policyCross), sizeof (policyCross));
1007+ hasPolicyCross = true ;
1008+ }
1009+ in->read (reinterpret_cast <char *>(threatGamCount), sizeof (threatGamCount));
1010+ in->read (reinterpret_cast <char *>(threatGamPair), sizeof (threatGamPair));
1011+ if (!hasValidThreatGamReferences (threatGamCount, threatGamPair))
1012+ return false ;
1013+ }
1014+ else
9101015 return false ;
9111016
912- if (in->peek () != std::ios::traits_type::eof () || in->bad ())
1017+ if (!*in || in->peek () != std::ios::traits_type::eof () || in->bad ())
9131018 return false ;
9141019 publishBaseTables ();
9151020 Evaluation::resetPolicyCross ();
916- for (size_t context = 0 ; context < ModelPolicyContextCount; context++)
917- for (size_t self = 0 ; self < PolicyCrossPatternCount; self++)
918- for (size_t opponent = 0 ; opponent < PolicyCrossPatternCount; opponent++)
919- Evaluation::POLICY_CROSS [FREESTYLE ][context]
920- [Evaluation::policyPatternFromStorageIndex (self)]
921- [Evaluation::policyPatternFromStorageIndex (opponent)] =
922- policyCross[context][self][opponent];
923- Evaluation::activatePolicyCross ();
1021+ if (hasPolicyCross) {
1022+ for (size_t context = 0 ; context < ModelPolicyContextCount; context++)
1023+ for (size_t self = 0 ; self < PolicyCrossPatternCount; self++)
1024+ for (size_t opponent = 0 ; opponent < PolicyCrossPatternCount; opponent++)
1025+ Evaluation::POLICY_CROSS [FREESTYLE ][context]
1026+ [Evaluation::policyPatternFromStorageIndex (self)]
1027+ [Evaluation::policyPatternFromStorageIndex (opponent)] =
1028+ policyCross[context][self][opponent];
1029+ Evaluation::activatePolicyCross ();
1030+ }
9241031 return true ;
9251032}
9261033
@@ -949,29 +1056,45 @@ void Config::exportModel(std::ostream &outStream)
9491056
9501057 bool writePolicyCross =
9511058 Evaluation::isPolicyCrossPresent () || !Evaluation::hasDefaultPolicyCross ();
952- if (!writePolicyCross)
1059+ bool writeThreatGam =
1060+ hasThreatGamValues (Evaluation::THREAT_GAM_COUNT , Evaluation::THREAT_GAM_PAIR );
1061+ if (!writePolicyCross && !writeThreatGam)
9531062 return ;
9541063 out->write (ClassicalModelExtensionMagic.data (), ClassicalModelExtensionMagic.size ());
955- uint32_t version = ClassicalModelCompactP3Version;
956- uint32_t payloadBytes = ClassicalModelCompactP3PayloadBytes;
957- uint32_t tableCount = ModelPolicyTableCount;
958- uint32_t contextCount = ModelPolicyContextCount;
1064+ uint32_t version =
1065+ writeThreatGam ? ClassicalModelThreatGamVersion : ClassicalModelCompactP3Version;
1066+ uint32_t payloadBytes = writeThreatGam ? ThreatGamExtraPayloadBytes
1067+ + (writePolicyCross ? PolicyCrossPayloadBytes : 0 )
1068+ : ClassicalModelCompactP3PayloadBytes;
1069+ uint32_t tableCount = writePolicyCross ? ModelPolicyTableCount : 0 ;
1070+ uint32_t contextCount = writePolicyCross ? ModelPolicyContextCount : 0 ;
9591071 uint32_t componentCount = ModelBlendComponentCount;
960- uint32_t patternCount = PolicyCrossPatternCount;
1072+ uint32_t patternCount = writePolicyCross ? PolicyCrossPatternCount : 0 ;
9611073 out->write (reinterpret_cast <const char *>(&version), sizeof (version));
9621074 out->write (reinterpret_cast <const char *>(&payloadBytes), sizeof (payloadBytes));
9631075 out->write (reinterpret_cast <const char *>(&tableCount), sizeof (tableCount));
9641076 out->write (reinterpret_cast <const char *>(&contextCount), sizeof (contextCount));
9651077 out->write (reinterpret_cast <const char *>(&componentCount), sizeof (componentCount));
9661078 out->write (reinterpret_cast <const char *>(&patternCount), sizeof (patternCount));
9671079
968- StoredPolicyCross policyCross;
969- for (size_t context = 0 ; context < ModelPolicyContextCount; context++)
970- for (size_t self = 0 ; self < PolicyCrossPatternCount; self++)
971- for (size_t opponent = 0 ; opponent < PolicyCrossPatternCount; opponent++)
972- policyCross[context][self][opponent] =
973- Evaluation::POLICY_CROSS [FREESTYLE ][context]
974- [Evaluation::policyPatternFromStorageIndex (self)]
975- [Evaluation::policyPatternFromStorageIndex (opponent)];
976- out->write (reinterpret_cast <const char *>(policyCross), sizeof (policyCross));
1080+ if (writePolicyCross) {
1081+ StoredPolicyCross policyCross;
1082+ for (size_t context = 0 ; context < ModelPolicyContextCount; context++)
1083+ for (size_t self = 0 ; self < PolicyCrossPatternCount; self++)
1084+ for (size_t opponent = 0 ; opponent < PolicyCrossPatternCount; opponent++)
1085+ policyCross[context][self][opponent] =
1086+ Evaluation::POLICY_CROSS [FREESTYLE ][context]
1087+ [Evaluation::policyPatternFromStorageIndex (self)]
1088+ [Evaluation::policyPatternFromStorageIndex (
1089+ opponent)];
1090+ out->write (reinterpret_cast <const char *>(policyCross), sizeof (policyCross));
1091+ }
1092+ if (writeThreatGam) {
1093+ if (!hasValidThreatGamReferences (Evaluation::THREAT_GAM_COUNT , Evaluation::THREAT_GAM_PAIR ))
1094+ throw std::runtime_error (" threat GAM reference cells must remain zero" );
1095+ out->write (reinterpret_cast <const char *>(Evaluation::THREAT_GAM_COUNT ),
1096+ sizeof (Evaluation::THREAT_GAM_COUNT ));
1097+ out->write (reinterpret_cast <const char *>(Evaluation::THREAT_GAM_PAIR ),
1098+ sizeof (Evaluation::THREAT_GAM_PAIR ));
1099+ }
9771100}
0 commit comments