From 212dbc1202cbc33702b7b69ce7c4d0bc213a140a Mon Sep 17 00:00:00 2001 From: Tom van Dijk Date: Sun, 4 Jul 2021 16:28:06 +0200 Subject: [PATCH 1/5] Added solver DFTL and counterexample --- CMakeLists.txt | 6 + README.md | 5 +- src/dftl.cpp | 744 +++++++++++++++++++++++++++++++++++++ src/dftl.hpp | 95 +++++ src/solvers.cpp | 3 + src/tools/counter_dftl.cpp | 84 +++++ 6 files changed, 936 insertions(+), 1 deletion(-) create mode 100644 src/dftl.cpp create mode 100644 src/dftl.hpp create mode 100644 src/tools/counter_dftl.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e31e2c8..097932b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,6 +97,7 @@ target_sources(oink src/zlkpp.cpp src/ptl.cpp src/dtl.cpp + src/dftl.cpp ${OINK_HDRS} ) @@ -178,6 +179,10 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) add_executable(counter_qpt src/tools/counter_qpt.cpp) set_target_props(counter_qpt) + add_executable(counter_dftl src/tools/counter_dftl.cpp) + set_target_props(counter_dftl) + target_link_libraries(counter_dftl oink) + add_executable(tc src/tools/tc.cpp) set_target_props(tc) target_link_libraries(tc oink) @@ -225,6 +230,7 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) add_test(NAME TestSolverSPPTL COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --spptl) add_test(NAME TestSolverDTL COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --dtl) add_test(NAME TestSolverIDTL COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --idtl) + add_test(NAME TestSolverDFTL COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --dftl) # test ZLK variations add_test(NAME TestSolverZLKseq COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --zlk -w -1) add_test(NAME TestSolverZLKpar COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --zlk -w 0) diff --git a/README.md b/README.md index d0acf96..026ed78 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ PTL | Progressive Tangle Learning (research variant) SPPTL | Single-player Progressive Tangle Learning (research variant) DTL | Distance Tangle Learning (research variant based on 'good path length') IDTL | Interleaved Distance Tangle Learning (interleaved variant of DTL) +DFTL | Distraction-Free Tangle Learning (research variant) +PDFTL | Prepartition Distraction-Free Tangle Learning (DFTL, prepartition variant) ### Fixpoint algorithms @@ -195,6 +197,7 @@ counter\_rob | SCC version of counter\_core. counter\_dtl | Counterexample to the DTL solver counter\_ortl | Counterexample to the ORTL solver counter\_symsi | Counterexample of Matthew Maat to (standard) symmetric strategy improvement +counter\_dftl | Counterexample to the DFTL solver tc | Two binary counters generator (game family that is an exponential lower bound for many algorithms). See also Tom van Dijk (2019) [A Parity Game Tale of Two Counters](https://doi.org/10.4204/EPTCS.305.8). In: GandALF 2019. tc+ | TC modified to defeat the RTL solver @@ -212,7 +215,7 @@ The two binary counters game family appears to be a quasi-polynomial lower bound * The progress measures variations SSPM, QPT Some algorithms can solve the two binary counters games in polynomial time: -* The tangle learning algorithms RTL, ORTL, PTL, SPPTL, DTL, IDTL +* The tangle learning algorithms RTL, ORTL, PTL, SPPTL, DTL, IDTL, DFTL, PDFTL * The strategy improvement algorithm PSI * Unsure: the BSSPM and BQPT algorithms diff --git a/src/dftl.cpp b/src/dftl.cpp new file mode 100644 index 0000000..104f177 --- /dev/null +++ b/src/dftl.cpp @@ -0,0 +1,744 @@ +/* + * Copyright 2020-2021 Tom van Dijk + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "dftl.hpp" + +namespace pg { + +/** + * The "distraction-free" tangle learning algorithm. + * + * PROCEDURE iteration(player) + * 1. Take -priority vertices V as targets + * 2. Tangle-attract to V from top to bottom. + * 3. Closed regions become tangles + * 4. Repeat 2-3 until no more closed regions + * 5. Remove lowest region top from V + * 6. Repeat 2-5 until V is empty + * + * Possible executions: + * A. Run iteration(even) until no new tangles found; then iteration(odd) until game is solved + * B. Run iteration(odd) until no new tangles found; then iteration(even) until game is solved + * C. Run iteration(even) once then iteration(odd) once; repeat until game is solved + * + * Each iteration of this algorithm runs in polynomial time and consists of O(n²) many "steps". + * Each "step" (2-3) is simply one iteration of single-player tangle learning. + * After each step, either there are fewer regions, or V is smaller. + * + * Each DFTL iteration starts with V set to all even-priority (or odd-priority) vertices. + * Each step then decomposes the game into regions dominated by the vertices in V. + * That is, starting with the highest v in V, attract all vertices+tangles with priority <= pr(v). + * Each highest vertex in v (the target of the attractor) is called the top vertex of its region. + * Every *closed* region is then analysed to find new tangles, as in standard tangle learning. + * If however no region is closed, then we assume that at least one vertex in V is a distraction. + * We assume that "surely" the lowest top vertex is distracting, so we remove that vertex from V. + * + * Every step thus either removes a vertex from V, or learns new tangles (removing a region). + * Hence each iteration has at most O(n²) steps and resuilts in at most O(n²) new tangles. + * + * For the possible executiong A, B, C, the counter_dftl generator is a lower bound that requires + * exponentially many steps. + * + * When the ``prepartition'' flag is set to True, this lower bound does not work anymore. It is + * yet unclear whether the counterexample can be adapted to deal with the prepartition variation. + * The prepartition variation modifies step 1 of iteration, by first computing a partition of the + * game into even regions and odd regions, and only considering all even/odd vertices in even/odd + * regions. In a way, this combines the "standard" mechanism of distraction removal (attract to + * opponent) with the remove-lowest-open-top mechanism. + */ + + +DFTLSolver::DFTLSolver(Oink *oink, Game *game) : Solver(oink, game) +{ +} + + +DFTLSolver::~DFTLSolver() +{ +} + + +/** + * Attract as player via to , vertices in from subgame . + * If >= 0, then only attract vertices with priority 0 <= pr <= max_prio. + * Add attracted vertices to and to queue . + * Update with the obtained attractor strategy. + */ +void +DFTLSolver::attractVertices(const int pl, const int v, bitset &R, bitset &Z, bitset &G, const int max_prio) +{ + // attract vertices with an edge to + for (auto curedge = ins(v); *curedge != -1; curedge++) { + int from = *curedge; + if (Z[from]) { + // already in Z, maybe set strategy (for vertices in the original target set) + if (owner(from) == pl and str[from] == -1) str[from] = v; + } else if (R[from] and (max_prio < 0 or priority(from) <= max_prio)) { + if (owner(from) != pl) { + // check if opponent can escape + bool escapes = false; + for (auto curedge = outs(from); *curedge != -1; curedge++) { + int to = *curedge; + if (G[to] and !Z[to]) { + escapes = true; + break; + } + } + if (escapes) continue; + } + // attract + Z[from] = true; + str[from] = owner(from) == pl ? v : -1; + Q.push(from); +#ifndef NDEBUG + // maybe report event + if (trace >= 3) { + logger << "\033[1;37mattracted \033[36m" << label_vertex(from) << "\033[m by \033[1;36m" << pl << "\033[m"; + if (owner(from) == pl) logger << " (via " << label_vertex(v) << ")" << std::endl; + else logger << " (forced)" << std::endl; + } +#endif + } + } +} + + +/** + * Try to attract tangle for player to attractor set . + * All vertices in tangle must be in and the opponent may not escape to subgame . + * Add attracted vertices to and to queue . + * Update with the obtained attractor strategy. + */ +bool +DFTLSolver::attractTangle(const int t, const int pl, bitset &R, bitset &Z, bitset &G, const int max_prio) +{ + /** + * Check if tangle is won by player and not deleted. + */ + { + const int tangle_pr = tpr[t]; + if (tangle_pr == -1) return false; // deleted tangle + if (pl != -1 and pl != (tangle_pr&1)) return false; // not of desired parity + } + + /** + * Check if tangle is contained in Z+R and if any vertices are not already in Z + * If no new vertices could be attracted, then don't attract... + */ + { + bool can_attract_new = false; + int *ptr = tv[t]; + for (;;) { + const int v = *ptr++; + if (v == -1) break; + ptr++; // skip strategy + if (!this->G[v]) { + // on-the-fly detect out-of-game tangles + tpr[t] = -1; // delete the tangle + return false; // is now a deleted tangle + } else if (Z[v]) { + continue; // already attracted + } else if (!R[v]) { + return false; // not contained in Z+R + } else if (max_prio >= 0 and priority(v) > max_prio) { + return false; + } else { + can_attract_new = true; // has vertices not yet attracted + } + } + if (!can_attract_new) return false; + } + + /** + * Check if the tangle can escape to G\Z. + */ + { + int v, *ptr = tout[t]; + while ((v=*ptr++) != -1) { + if (Z[v]) continue; + if (G[v]) return false; // opponent escapes + } + } + + /** + * Attract! + */ + { + int *ptr = tv[t]; + for (;;) { + const int v = *ptr++; + if (v == -1) break; + const int s = *ptr++; + if (Z[v]) continue; // already in + Z[v] = true; + str[v] = s; + Q.push(v); + +#ifndef NDEBUG + // maybe report event + if (trace >= 3) { + logger << "\033[1;37mattracted \033[36m" << label_vertex(v) << "\033[m by \033[1;36m" << pl << "\033[m"; + logger << " (via tangle " << t << ")" << std::endl; + } +#endif + } + } + + return true; +} + + +/** + * Attract vertices that are in tangles. + * Current attracting set is . + * Current subgame is . + * Only tangles that are contained in + and vertices of maximum priority max_prio (if >= 0) + * Write strategy to . + * Current attracting vertex is . + * Attracting for player . + * (for trace) Attracting to region with priority . + */ +inline void +DFTLSolver::attractTangles(const int pl, int v, bitset &R, bitset &Z, bitset &G, const int max_prio) +{ + const auto &in_cur = tin[v]; + for (int from : in_cur) attractTangle(from, pl, R, Z, G, max_prio); +} + + +/** + * Compute SCCs in subgraph induced by and . + * Start the SCC computation at vertex . + * Every SCC is then processed as a tangle. + * If the tangle is closed, it is a dominion and added to and . + */ +bool +DFTLSolver::extractTangles(int startvertex, bitset &R, int *str) +{ + bool new_tangles = false; + const int pr = priority(startvertex); + const int pl = pr&1; + + /** + * The following is the nonrecursive implementation of David Pearce, + * "A space-efficient algorithm for finding strongly connected components" (IPL, 2016), + * modified to on-the-fly restrict the graph by and . + */ + + // beginVisiting + pea_vS.push(startvertex); + pea_iS.push(0); + pea_root[startvertex] = true; + pea_vidx[startvertex] = pea_curidx++; + while (pea_vS.nonempty()) { +pearce_again: + // visitLoop + const unsigned int n = pea_vS.back(); + unsigned int i = pea_iS.back(); + + if (owner(n) != pl) { + auto edges = outs(n); + if (i>0) { + // finishEdge + const int w = edges[i-1]; + if (pea_vidx[w] < pea_vidx[n]) { + pea_vidx[n] = pea_vidx[w]; + pea_root[n] = false; + } + } + for (;;) { + const int to = edges[i]; + if (to == -1) break; // done + // beginEdge + if (R[to]) { + if (pea_vidx[to] == 0) { + pea_iS.back() = i+1; + // beginVisiting + pea_vS.push(to); + pea_iS.push(0); + pea_root[to] = true; + pea_vidx[to] = pea_curidx++; + goto pearce_again; // break; continue; + } else { + // finishEdge + if (pea_vidx[to] < pea_vidx[n]) { + pea_vidx[n] = pea_vidx[to]; + pea_root[n] = false; + } + } + } + i++; + } + } else { + const int s = str[n]; + if (i == 0) { + // beginEdge + if (pea_vidx[s] == 0) { + pea_iS.back() = 1; + // beginVisiting + pea_vS.push(s); + pea_iS.push(0); + pea_root[s] = true; + pea_vidx[s] = pea_curidx++; + goto pearce_again; // break; continue; + } + } + // finishEdge + if (pea_vidx[s] < pea_vidx[n]) { + pea_vidx[n] = pea_vidx[s]; + pea_root[n] = false; + } + } + // finishVisiting + pea_vS.pop(); + pea_iS.pop(); + if (pea_root[n]) { + pea_curidx -= 1; + tangle.push_back(n); + while (pea_S.nonempty()) { + const int t = pea_S.back(); + if (pea_vidx[n]>pea_vidx[t]) break; + pea_S.pop(); + pea_curidx -= 1; + pea_vidx[t] = (unsigned int)-1; + tangle.push_back(t); + } + pea_vidx[n] = (unsigned int)-1; + } else { + pea_S.push(n); + continue; + } + + /** + * End of Pearce's algorithm. + * At this point, we have an SCC in . + * + * Now check if the SCC is nontrivial, i.e., contains a cycle, i.e., either 2+ vertices + * or a self-loop. + */ + + const bool is_tangle = (tangle.size() > 1) or + ((unsigned int)str[n] == n) or + (str[n] == -1 and game->has_edge(n, n)); + if (!is_tangle) { + tangle.clear(); + continue; + } + + /** + * We have a tangle. Compute the outgoing edges (into ) and the next highest region. + */ + + for (const int v : tangle) escapes[v] = true; + + for (const int v : tangle) { + if (owner(v) != pl) { + for (auto curedge = outs(v); *curedge != -1; curedge++) { + int to = *curedge; + if (G[to] and !escapes[to]) { + escapes[to] = true; + tangleto.push(to); + } + } + } + } + + escapes.reset(); + + /** + * If there are no outgoing edges, then we have found a dominion. + */ + + if (tangleto.empty()) { + // dominion + if (trace) { + logger << "\033[1;38;5;201mdominion \033[36m" << pr << "\033[m"; +#ifndef NDEBUG + if (trace >= 2) { + for (const int v : tangle) { + logger << " \033[1;36m" << label_vertex(v) << "\033[m"; + if (str[v] != -1) logger << "->" << label_vertex(str[v]); + } + } +#endif + logger << std::endl; + } + for (const int v : tangle) { + // if not yet added to solve queue, mark and add it + if (S[v] == false) { + S[v] = true; + dom_vector.push_back(v); + dom_vector.push_back(str[v]); + } + } + dominions++; + tangle.clear(); + new_tangles = true; + continue; + } + + /** + * We're not a dominion, we're a tangle. + */ + + if (trace >= 1) { + logger << "\033[1;38;5;198mnew tangle " << pr << "\033[m (" << tpr.size() << ")"; + if (trace >= 2) { + for (const int v : tangle) { + logger << " \033[1;36m" << label_vertex(v) << "\033[m"; + if (str[v] != -1) logger << "->" << label_vertex(str[v]); + } + } + logger << " with " << tangleto.size() << " escape vertices."; + logger << std::endl; + } + + // new tangle idx + const int tidx = tpr.size(); + + // add back links to all normal vertices in our [out] + for (unsigned int x = 0; x < tangleto.size(); x++) tin[tangleto[x]].push_back(tidx); + + // move tangleto into vout + int* _tout = new int[tangleto.size()+1]; + std::copy(&tangleto[0], &tangleto[tangleto.size()], _tout); + _tout[tangleto.size()] = -1; + tout.push_back(_tout); + + // move tangle into vv + int* _vv = new int[tangle.size()*2+1], c=0; + for (const int v : tangle) { + _vv[c++] = v; + _vv[c++] = str[v]; + } + _vv[c] = -1; + tv.push_back(_vv); + + // and set p to pr + tpr.push_back(pr); + + tangles++; + new_tangles = true; + tangle.clear(); + tangleto.clear(); + } + + pea_S.clear(); + return new_tangles; +} + + +/** + * Single-player tangle learning. + * Find new tangles for given a set of vertices in the subgame . + * is typically the full remaining game. + */ +bool +DFTLSolver::sptl(bitset &V, bitset &R, const int player, int &lowest_top) +{ + bool changes = false; + lowest_top = -1; + + for (auto top = V.find_last(); top != bitset::npos; top = V.find_prev(top)) { + if (!R[top]) continue; + + lowest_top = top; + Z[top] = true; // add to + str[top] = -1; + Q.push(top); + + while (Q.nonempty()) { + const int v = Q.pop(); + R[v] = false; // remove from + attractVertices(player, v, R, Z, R, priority(top)); + attractTangles(player, v, R, Z, R, priority(top)); + } + +#ifndef NDEBUG + if (trace >= 2) { + // report region + logger << "\033[1;33mregion\033[m \033[1;36m" << priority(top) << "\033[m"; + for (auto v = Z.find_last(); v != bitset::npos; v = Z.find_prev(v)) { + logger << " \033[1;38;5;15m" << label_vertex(v) << "\033[m"; + if (str[v] != -1) logger << "->" << label_vertex(str[v]); + } + logger << std::endl; + } +#endif + + bool closed_region = true; + + if (owner(top) == player) { + closed_region = (str[top] != -1); + } else { + for (auto curedge = outs(top); *curedge != -1; curedge++) { + if (R[*curedge]) { + closed_region = false; + break; + } + } + } + + if (closed_region) { + /** + * Extract tangles by computing bottom SCCs starting at each top vertex. + * Note: each bottom SCC must contain a top vertex. + */ + + std::fill(pea_vidx, pea_vidx+nodecount(), '\0'); + pea_curidx = 1; + + if (extractTangles(top, Z, str)) changes = true; + } + + Z.reset(); + } + + return changes; +} + + +void +DFTLSolver::partition(bitset &R, int top, bitset &Even, bitset &Odd) +{ + for (; top!=-1 ;top--) { + if (!R[top]) continue; + + const int pl = priority(top)&1; + auto &Z = pl == 0 ? Even : Odd; + +#ifndef NDEBUG + if (trace >= 2) W = Z; +#endif + + Z[top] = true; // add to + str[top] = -1; + Q.push(top); + + while (Q.nonempty()) { + const int v = Q.pop(); + R[v] = false; // remove from + attractVertices(pl, v, R, Z, R, priority(top)); + attractTangles(pl, v, R, Z, R, priority(top)); + } + +#ifndef NDEBUG + if (trace >= 2) { + // report region + W ^= Z; + logger << "\033[1;33mregion\033[m "; + logger << "\033[1;36m" << priority(top) << "\033[m"; + for (auto v = W.find_last(); v != bitset::npos; v = W.find_prev(v)) { + logger << " \033[1;38;5;15m" << label_vertex(v) << "\033[m"; + if (str[v] != -1) logger << "->" << label_vertex(str[v]); + } + logger << std::endl; + } +#endif + } +} + + +bool +DFTLSolver::search(const int player) +{ + const int T = tangles; + const int D = dominions; + + // just start with V is all 's vertices + V = G; + if (player == 0) V -= Parity; + else V &= Parity; + + // if we restrict V based on partition, do that now + if (prepartition) { +#ifndef NDEBUG + if (trace) { + logger << "\033[1;38;5;33mcomputing initial partition\033[m " << std::endl; + } +#endif + + CurG = G; + Even.reset(); + Odd.reset(); + partition(CurG, nodecount()-1, Even, Odd); + + if (player == 0) V &= Even; + else V &= Odd; + +#ifndef NDEBUG + if (trace) { + logger << "\033[1;38;5;33mselected vertices\033[m:"; + for (auto v = V.find_last(); v != bitset::npos; v = V.find_prev(v)) { + logger << " \033[1;38;5;15m" << label_vertex(v) << "\033[m"; + } + logger << std::endl; + logger << "\033[1;38;5;33mrunning SPTL steps\033[m " << std::endl; + } +#endif + } + + while (V.any()) { + steps++; + + CurG = G; + int lowest_top; + if (sptl(V, CurG, player, lowest_top)) { + // Extend any dominions that were found. + // (Any solved vertices are now in and .) + if (!dom_vector.empty()) { + for (unsigned i = 0; isolve(v, player, str[v]); + attractVertices(player, v, G, S, G, -1); + attractTangles(player, v, G, S, G, -1); + } + + V -= S; // remove from V + G -= S; // remove from G + S.reset(); + } + } else { + // No new tangles, so remove the lowest top + // We assume it is a distraction + V[lowest_top] = false; +#ifndef NDEBUG + if (trace >= 1) { + logger << "\033[1;38;5;33mremoving \033[36m" << label_vertex(lowest_top) << "\033[m" << std::endl; + } +#endif + } + } + + return tangles != T or dominions != D; +} + + +void +DFTLSolver::run() +{ + tin = new std::vector[nodecount()]; + str = new int[nodecount()]; + + Z.resize(nodecount()); + S.resize(nodecount()); + G = disabled; + G.flip(); + CurG.resize(nodecount()); + V.resize(nodecount()); + W.resize(nodecount()); + + Parity.resize(nodecount()); + for (int v=0; v tout; // for each tangle + std::vector *tin; // for each normal vertex + std::vector tv; // the tangle (vertex-strategy pairs) + std::vector tpr; // priority of a tangle + + uintqueue Q; // main queue when attracting vertices + std::vector dom_vector; // stores the solved vertices (in dominions) + + int *str; // stores currently assigned strategy of each vertex + + uintqueue pea_vS; // vS + uintqueue pea_iS; // iS + uintqueue pea_S; // S + unsigned int* pea_vidx; // rindex + bitset pea_root; // root + int pea_curidx; // index + + std::vector tangle; // stores the new tangle + uintqueue tangleto; // stores the vertices the tangle can escape to + bitset escapes; // which escapes we considered + + bitset Z; // current region (in sptl) + bitset G; // the unsolved game + bitset S; // solved vertices (in queue Q) + bitset CurG; // in search + + bitset V; + bitset W; + + bitset Parity; // vertices of parity 0/1 + bitset Even; // for partition + bitset Odd; // for partition + + inline void attractVertices(const int pl, int v, bitset &R, bitset &Z, bitset &G, const int max_prio); + bool attractTangle(const int t, const int pl, bitset &R, bitset &Z, bitset &G, const int max_prio); + inline void attractTangles(const int pl, int v, bitset &R, bitset &Z, bitset &G, const int max_prio); + + bool search(const int player); + void partition(bitset &R, int top, bitset &Even, bitset &Odd); + bool sptl(bitset &V, bitset &R, const int player, int &lowest_top); + bool extractTangles(int i, bitset &R, int *str); +}; + +class PDFTLSolver : public DFTLSolver +{ +public: + PDFTLSolver(Oink *oink, Game *game) : DFTLSolver(oink, game) { prepartition = true; } + virtual ~PDFTLSolver() { } +}; + + + +} + +#endif diff --git a/src/solvers.cpp b/src/solvers.cpp index 807345a..0d213dc 100644 --- a/src/solvers.cpp +++ b/src/solvers.cpp @@ -39,6 +39,7 @@ #include "ppq.hpp" #include "ptl.hpp" #include "dtl.hpp" +#include "dftl.hpp" namespace pg { @@ -73,6 +74,8 @@ Solvers::Solvers() add("spptl", "single-player progressive tangle learning", 0, [] (Oink* oink, Game* game) { return new SPPTLSolver(oink, game); }); add("dtl", "distance tangle learning", 0, [] (Oink* oink, Game* game) { return new DTLSolver(oink, game); }); add("idtl", "interleaved distance tangle learning", 0, [] (Oink* oink, Game* game) { return new IDTLSolver(oink, game); }); + add("dftl", "distraction-free tangle learning", 0, [] (Oink* oink, Game* game) { return new DFTLSolver(oink, game); }); + add("pdftl", "prepartition distraction-free tangle learning", 0, [] (Oink* oink, Game* game) { return new PDFTLSolver(oink, game); }); add("rtl", "recursive tangle learning", 0, [] (Oink* oink, Game* game) { return new RTLSolver(oink, game); }); add("ortl", "one-sided recursive tangle learning", 0, [] (Oink* oink, Game* game) { return new ORTLSolver(oink, game); }); add("tl", "tangle learning", 0, [] (Oink* oink, Game* game) { return new TLSolver(oink, game); }); diff --git a/src/tools/counter_dftl.cpp b/src/tools/counter_dftl.cpp new file mode 100644 index 0000000..da77846 --- /dev/null +++ b/src/tools/counter_dftl.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2021 Tom van Dijk, Johannes Kepler University Linz + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +using namespace pg; + +int +main(int argc, char** argv) +{ + if (argc < 2) { + std::cout << "Syntax: " << argv[0] << " N" << std::endl; + return -1; + } + + const int n = std::stoi(argv[1]); // index of game + const int piv = 2+2*n; // pivot priority + + Game game(8 + 4*n); + + game.vec_init(); + + game.init_vertex(0, piv+6, 1); // top vertex of dominion + game.init_vertex(1, 0, 1); // first [distracted] tangle + game.init_vertex(2, piv+5, 1); // barrier + game.init_vertex(3, 2, 1); // second [distracted] tangle + game.init_vertex(4, 2, 1); // second [distracted] tangle "self-loop" assist + game.vec_add_edge(0, 3); // top vertex -> second tangle + game.vec_add_edge(1, 0); // first tangle -> top vertex + game.vec_add_edge(2, 1); // barrier -> first tangle + game.vec_add_edge(3, 2); // second tangle -> barrier + game.vec_add_edge(3, 4); // second tangle -> self-loop assist + game.vec_add_edge(4, 3); // self-loop assist -> second tangle + + game.init_vertex(5, piv+2, 0); // extra distracted vertex connected to first tangle + game.init_vertex(6, piv+4, 0); // distraction + game.init_vertex(7, piv+5, 0); // dominant + game.vec_add_edge(1, 5); // first tangle -> distracted vertex + game.vec_add_edge(5, 1); // distracted vertex -> first tangle + game.vec_add_edge(5, 6); // distracted vertex -> distraction + game.vec_add_edge(6, 7); // distraction -> dominant + game.vec_add_edge(7, 5); // dominant -> distracted vertex + + for (int i=0; i distracted vertex 1 + game.vec_add_edge(c+0, 1); // distracted vertex 1 -> first tangle + game.vec_add_edge(3, c+1); // second tangle -> distracted vertex 2 + game.vec_add_edge(c+1, 3); // distracted vertex 2 -> second tangle + game.vec_add_edge(c+0, c+2); // distracted vertex 1 -> distraction + game.vec_add_edge(c+1, c+2); // distracted vertex 2 -> distraction + game.vec_add_edge(c+2, c+3); // distraction -> dominant + game.vec_add_edge(c+3, c+1); // dominant -> distracted vertex 2 + } + + game.vec_finish(); + + //game.sort(); // optional sort vertices + //game.renumber(); // optional renumber vertices for tight priorities + + game.write_pgsolver(std::cout); +} From fed9cc543f151ae03c16dde460b3bf280b4f99ab Mon Sep 17 00:00:00 2001 From: Tom van Dijk Date: Tue, 18 Jun 2024 21:08:13 +0200 Subject: [PATCH 2/5] Prepare for making DFTL two-player and keeping the original one-player version --- CMakeLists.txt | 7 ++++--- README.md | 4 ++-- src/dftl.hpp | 7 ++++--- src/solvers.cpp | 2 +- src/tools/{counter_dftl.cpp => counter_odftl.cpp} | 0 5 files changed, 11 insertions(+), 9 deletions(-) rename src/tools/{counter_dftl.cpp => counter_odftl.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 097932b..c1fe1ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,9 +179,9 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) add_executable(counter_qpt src/tools/counter_qpt.cpp) set_target_props(counter_qpt) - add_executable(counter_dftl src/tools/counter_dftl.cpp) - set_target_props(counter_dftl) - target_link_libraries(counter_dftl oink) + add_executable(counter_odftl src/tools/counter_odftl.cpp) + set_target_props(counter_odftl) + target_link_libraries(counter_odftl oink) add_executable(tc src/tools/tc.cpp) set_target_props(tc) @@ -231,6 +231,7 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) add_test(NAME TestSolverDTL COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --dtl) add_test(NAME TestSolverIDTL COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --idtl) add_test(NAME TestSolverDFTL COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --dftl) + add_test(NAME TestSolverODFTL COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --odftl) # test ZLK variations add_test(NAME TestSolverZLKseq COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --zlk -w -1) add_test(NAME TestSolverZLKpar COMMAND test_solvers ${CMAKE_CURRENT_SOURCE_DIR}/tests --zlk -w 0) diff --git a/README.md b/README.md index 026ed78..c29c805 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,8 @@ PTL | Progressive Tangle Learning (research variant) SPPTL | Single-player Progressive Tangle Learning (research variant) DTL | Distance Tangle Learning (research variant based on 'good path length') IDTL | Interleaved Distance Tangle Learning (interleaved variant of DTL) -DFTL | Distraction-Free Tangle Learning (research variant) -PDFTL | Prepartition Distraction-Free Tangle Learning (DFTL, prepartition variant) +DFTL | Distraction-Free Tangle Learning (two-player variant) +ODFTL | One-player Distraction-Free Tangle Learning ### Fixpoint algorithms diff --git a/src/dftl.hpp b/src/dftl.hpp index 8cff0bd..da12579 100644 --- a/src/dftl.hpp +++ b/src/dftl.hpp @@ -31,6 +31,7 @@ class DFTLSolver : public Solver protected: bool prepartition = false; // occasionally may result in MORE iterations + bool oneplayer = false; // like ORTL, only consider vertices of one player bool interleaved = true; int iterations = 0; @@ -81,11 +82,11 @@ class DFTLSolver : public Solver bool extractTangles(int i, bitset &R, int *str); }; -class PDFTLSolver : public DFTLSolver +class ODFTLSolver : public DFTLSolver { public: - PDFTLSolver(Oink *oink, Game *game) : DFTLSolver(oink, game) { prepartition = true; } - virtual ~PDFTLSolver() { } + ODFTLSolver(Oink *oink, Game *game) : DFTLSolver(oink, game) { oneplayer = true; } + virtual ~ODFTLSolver() { } }; diff --git a/src/solvers.cpp b/src/solvers.cpp index 0d213dc..8ee9143 100644 --- a/src/solvers.cpp +++ b/src/solvers.cpp @@ -75,7 +75,7 @@ Solvers::Solvers() add("dtl", "distance tangle learning", 0, [] (Oink* oink, Game* game) { return new DTLSolver(oink, game); }); add("idtl", "interleaved distance tangle learning", 0, [] (Oink* oink, Game* game) { return new IDTLSolver(oink, game); }); add("dftl", "distraction-free tangle learning", 0, [] (Oink* oink, Game* game) { return new DFTLSolver(oink, game); }); - add("pdftl", "prepartition distraction-free tangle learning", 0, [] (Oink* oink, Game* game) { return new PDFTLSolver(oink, game); }); + add("odftl", "one-player distraction-free tangle learning", 0, [] (Oink* oink, Game* game) { return new ODFTLSolver(oink, game); }); add("rtl", "recursive tangle learning", 0, [] (Oink* oink, Game* game) { return new RTLSolver(oink, game); }); add("ortl", "one-sided recursive tangle learning", 0, [] (Oink* oink, Game* game) { return new ORTLSolver(oink, game); }); add("tl", "tangle learning", 0, [] (Oink* oink, Game* game) { return new TLSolver(oink, game); }); diff --git a/src/tools/counter_dftl.cpp b/src/tools/counter_odftl.cpp similarity index 100% rename from src/tools/counter_dftl.cpp rename to src/tools/counter_odftl.cpp From 5199658352180e363dd5d7f041de5a11dcf3eade Mon Sep 17 00:00:00 2001 From: Tom van Dijk Date: Wed, 19 Jun 2024 00:06:14 +0200 Subject: [PATCH 3/5] Improved DFTL version with better two-player interaction --- src/dftl.cpp | 245 +++++++++++++++++++++++++++++++++------------------ src/dftl.hpp | 11 ++- 2 files changed, 163 insertions(+), 93 deletions(-) diff --git a/src/dftl.cpp b/src/dftl.cpp index 104f177..3df0ae3 100644 --- a/src/dftl.cpp +++ b/src/dftl.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Tom van Dijk + * Copyright 2020-2024 Tom van Dijk * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,17 +22,20 @@ namespace pg { * The "distraction-free" tangle learning algorithm. * * PROCEDURE iteration(player) - * 1. Take -priority vertices V as targets + * 1. Take all remaining vertices V as targets * 2. Tangle-attract to V from top to bottom. - * 3. Closed regions become tangles - * 4. Repeat 2-3 until no more closed regions - * 5. Remove lowest region top from V - * 6. Repeat 2-5 until V is empty + * 3. Closed regions of become tangles + * 4. (Optional: Repeat 2-3 until no more closed regions.) + * 5. Remove the lowest 's region top from V if that region was open. + * 6. Repeat 2-5 until there are no more regions of , or no vertices of in V + * + * Here, player==0 or player==1 or player==-1 meaning both players. * * Possible executions: * A. Run iteration(even) until no new tangles found; then iteration(odd) until game is solved * B. Run iteration(odd) until no new tangles found; then iteration(even) until game is solved * C. Run iteration(even) once then iteration(odd) once; repeat until game is solved + * D. Run iterations for both players simultaneously until the game is solved. * * Each iteration of this algorithm runs in polynomial time and consists of O(n²) many "steps". * Each "step" (2-3) is simply one iteration of single-player tangle learning. @@ -49,15 +52,9 @@ namespace pg { * Every step thus either removes a vertex from V, or learns new tangles (removing a region). * Hence each iteration has at most O(n²) steps and resuilts in at most O(n²) new tangles. * - * For the possible executiong A, B, C, the counter_dftl generator is a lower bound that requires - * exponentially many steps. - * - * When the ``prepartition'' flag is set to True, this lower bound does not work anymore. It is - * yet unclear whether the counterexample can be adapted to deal with the prepartition variation. - * The prepartition variation modifies step 1 of iteration, by first computing a partition of the - * game into even regions and odd regions, and only considering all even/odd vertices in even/odd - * regions. In a way, this combines the "standard" mechanism of distraction removal (attract to - * opponent) with the remove-lowest-open-top mechanism. + * For executions A (and B), the counter_dftl generator is an exponential lower bound. + * For execution D, the tc+ generator is an exponential lower bound. + * For execution C, no lower bound is yet known. */ @@ -378,6 +375,8 @@ DFTLSolver::extractTangles(int startvertex, bitset &R, int *str) } for (const int v : tangle) { // if not yet added to solve queue, mark and add it + auto &S = pl == 0 ? S0 : S1; + auto &dom_vector = pl == 0 ? dom_vector_0 : dom_vector_1; if (S[v] == false) { S[v] = true; dom_vector.push_back(v); @@ -440,6 +439,82 @@ DFTLSolver::extractTangles(int startvertex, bitset &R, int *str) return new_tangles; } +/** + * Two-player tangle learning. + * Find new tangles for given a set of vertices in the subgame . + * is typically the full remaining game. + */ +bool +DFTLSolver::tl(bitset &V, bitset &R, const int player, int &lowest_open_0, int &lowest_open_1) +{ + bool changes = false; + lowest_open_0 = -2; + lowest_open_1 = -2; + + for (auto top = V.find_last(); top != bitset::npos; top = V.find_prev(top)) { + if (!R[top]) continue; + + Z[top] = true; // add to + str[top] = -1; + Q.push(top); + + const int pl = priority(top) & 1; + (pl == 0 ? lowest_open_0 : lowest_open_1) = top; + + while (Q.nonempty()) { + const int v = Q.pop(); + R[v] = false; // remove from + attractVertices(pl, v, R, Z, R, priority(top)); + attractTangles(pl, v, R, Z, R, priority(top)); + } + +#ifndef NDEBUG + if (trace >= 2) { + // report region + logger << "\033[1;33mregion\033[m \033[1;36m" << priority(top) << "\033[m"; + for (auto v = Z.find_last(); v != bitset::npos; v = Z.find_prev(v)) { + logger << " \033[1;38;5;15m" << label_vertex(v) << "\033[m"; + if (str[v] != -1) logger << "->" << label_vertex(str[v]); + } + logger << std::endl; + } +#endif + + bool closed_region = true; + + if (owner(top) == pl) { + closed_region = (str[top] != -1); + } else { + for (auto curedge = outs(top); *curedge != -1; curedge++) { + if (R[*curedge]) { + closed_region = false; + break; + } + } + } + + if (closed_region) { + (pl == 0 ? lowest_open_0 : lowest_open_1) = -1; + + if (player == -1 or pl == player) { + /** + * Extract tangles by computing bottom SCCs starting at each top vertex. + * Note: each bottom SCC must contain a top vertex. + */ + + std::fill(pea_vidx, pea_vidx+nodecount(), '\0'); + pea_curidx = 1; + + if (extractTangles(top, Z, str)) changes = true; + } + } + + Z.reset(); + } + + return changes; +} + /** * Single-player tangle learning. @@ -560,75 +635,67 @@ DFTLSolver::search(const int player) // just start with V is all 's vertices V = G; - if (player == 0) V -= Parity; - else V &= Parity; - - // if we restrict V based on partition, do that now - if (prepartition) { -#ifndef NDEBUG - if (trace) { - logger << "\033[1;38;5;33mcomputing initial partition\033[m " << std::endl; - } -#endif - - CurG = G; - Even.reset(); - Odd.reset(); - partition(CurG, nodecount()-1, Even, Odd); - - if (player == 0) V &= Even; - else V &= Odd; - -#ifndef NDEBUG - if (trace) { - logger << "\033[1;38;5;33mselected vertices\033[m:"; - for (auto v = V.find_last(); v != bitset::npos; v = V.find_prev(v)) { - logger << " \033[1;38;5;15m" << label_vertex(v) << "\033[m"; - } - logger << std::endl; - logger << "\033[1;38;5;33mrunning SPTL steps\033[m " << std::endl; - } -#endif - } + bitset U(V); + if (player == 0) U -= Parity; + if (player == 1) U &= Parity; - while (V.any()) { + while (U.any()) { steps++; CurG = G; - int lowest_top; - if (sptl(V, CurG, player, lowest_top)) { + int lowest_open_0, lowest_open_1; + if (tl(V, CurG, player, lowest_open_0, lowest_open_1)) { // Extend any dominions that were found. // (Any solved vertices are now in and .) - if (!dom_vector.empty()) { - for (unsigned i = 0; isolve(v, player, str[v]); - attractVertices(player, v, G, S, G, -1); - attractTangles(player, v, G, S, G, -1); - } + while (Q.nonempty()) { + const int v = Q.pop(); + oink->solve(v, pl, str[v]); + attractVertices(pl, v, G, S, G, -1); + attractTangles(pl, v, G, S, G, -1); + } - V -= S; // remove from V - G -= S; // remove from G - S.reset(); + V -= S; // remove from V + U -= S; // remove from U + G -= S; // remove from G + S.reset(); + } } - } else { - // No new tangles, so remove the lowest top - // We assume it is a distraction - V[lowest_top] = false; + // continue; // uncomment to repeat TL until no new tangles are found + // otherwise: remove lowest open even if higher regions are closed + } + // logger << "lowest open 0 is " << lowest_open_0 << " and lowest open 1 is " << lowest_open_1 << std::endl; + if (lowest_open_0 >= 0 && player != 1) { + V[lowest_open_0] = false; + U[lowest_open_0] = false; #ifndef NDEBUG if (trace >= 1) { - logger << "\033[1;38;5;33mremoving \033[36m" << label_vertex(lowest_top) << "\033[m" << std::endl; + logger << "\033[1;38;5;33mremoving \033[36m" << label_vertex(lowest_open_0) << "\033[m" << std::endl; } #endif } + if (lowest_open_1 >= 0 && player != 0) { + V[lowest_open_1] = false; + U[lowest_open_1] = false; +#ifndef NDEBUG + if (trace >= 1) { + logger << "\033[1;38;5;33mremoving \033[36m" << label_vertex(lowest_open_1) << "\033[m" << std::endl; + } +#endif + } + if (player == 0 && lowest_open_0 == -2) break; // no more regions of player Even + if (player == 1 && lowest_open_1 == -2) break; // no more regions of player Odd } return tangles != T or dominions != D; @@ -642,7 +709,8 @@ DFTLSolver::run() str = new int[nodecount()]; Z.resize(nodecount()); - S.resize(nodecount()); + S0.resize(nodecount()); + S1.resize(nodecount()); G = disabled; G.flip(); CurG.resize(nodecount()); @@ -652,11 +720,6 @@ DFTLSolver::run() Parity.resize(nodecount()); for (int v=0; v tpr; // priority of a tangle uintqueue Q; // main queue when attracting vertices - std::vector dom_vector; // stores the solved vertices (in dominions) + std::vector dom_vector_0; // stores the solved vertices (in dominions) + std::vector dom_vector_1; // stores the solved vertices (in dominions) int *str; // stores currently assigned strategy of each vertex @@ -62,15 +61,14 @@ class DFTLSolver : public Solver bitset Z; // current region (in sptl) bitset G; // the unsolved game - bitset S; // solved vertices (in queue Q) + bitset S0; // solved vertices (in dom_vector_0) + bitset S1; // solved vertices (in dom_vector_1) bitset CurG; // in search bitset V; bitset W; bitset Parity; // vertices of parity 0/1 - bitset Even; // for partition - bitset Odd; // for partition inline void attractVertices(const int pl, int v, bitset &R, bitset &Z, bitset &G, const int max_prio); bool attractTangle(const int t, const int pl, bitset &R, bitset &Z, bitset &G, const int max_prio); @@ -78,6 +76,7 @@ class DFTLSolver : public Solver bool search(const int player); void partition(bitset &R, int top, bitset &Even, bitset &Odd); + bool tl(bitset &V, bitset &R, const int player, int &lowest_open_0, int &lowest_open_1); bool sptl(bitset &V, bitset &R, const int player, int &lowest_top); bool extractTangles(int i, bitset &R, int *str); }; From bd4269d898b56687a198bd0ca1168b31ead07238 Mon Sep 17 00:00:00 2001 From: Tom van Dijk Date: Wed, 19 Jun 2024 00:11:12 +0200 Subject: [PATCH 4/5] Fix counter_odftl to have a tiny tangle of the opponent after the distraction --- src/tools/counter_odftl.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/tools/counter_odftl.cpp b/src/tools/counter_odftl.cpp index da77846..1a4b088 100644 --- a/src/tools/counter_odftl.cpp +++ b/src/tools/counter_odftl.cpp @@ -31,7 +31,7 @@ main(int argc, char** argv) const int n = std::stoi(argv[1]); // index of game const int piv = 2+2*n; // pivot priority - Game game(8 + 4*n); + Game game(8 + 6*n); game.vec_init(); @@ -57,13 +57,15 @@ main(int argc, char** argv) game.vec_add_edge(7, 5); // dominant -> distracted vertex for (int i=0; i distracted vertex 1 game.vec_add_edge(c+0, 1); // distracted vertex 1 -> first tangle @@ -71,8 +73,11 @@ main(int argc, char** argv) game.vec_add_edge(c+1, 3); // distracted vertex 2 -> second tangle game.vec_add_edge(c+0, c+2); // distracted vertex 1 -> distraction game.vec_add_edge(c+1, c+2); // distracted vertex 2 -> distraction - game.vec_add_edge(c+2, c+3); // distraction -> dominant + game.vec_add_edge(c+2, c+4); // distraction -> dominant game.vec_add_edge(c+3, c+1); // dominant -> distracted vertex 2 + game.vec_add_edge(c+4, c+5); // tangle + game.vec_add_edge(c+5, c+4); // tangle + game.vec_add_edge(c+4, c+3); // dominant -> distracted vertex 2 } game.vec_finish(); From e2c45b75f1a44bc48ca682d56e4e332cfd650095 Mon Sep 17 00:00:00 2001 From: Tom van Dijk Date: Mon, 24 Jun 2024 12:50:34 +0200 Subject: [PATCH 5/5] Slight refactor to test a hypothesis --- src/dftl.cpp | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/dftl.cpp b/src/dftl.cpp index 3df0ae3..7a2abc4 100644 --- a/src/dftl.cpp +++ b/src/dftl.cpp @@ -459,7 +459,6 @@ DFTLSolver::tl(bitset &V, bitset &R, const int player, int &lowest_open_0, int & Q.push(top); const int pl = priority(top) & 1; - (pl == 0 ? lowest_open_0 : lowest_open_1) = top; while (Q.nonempty()) { const int v = Q.pop(); @@ -494,19 +493,20 @@ DFTLSolver::tl(bitset &V, bitset &R, const int player, int &lowest_open_0, int & } if (closed_region) { + // comment the following line for a variation that removes + // top vertices of the lowest region that is open, instead of + // the lowest region *if* it is open (pl == 0 ? lowest_open_0 : lowest_open_1) = -1; if (player == -1 or pl == player) { - /** - * Extract tangles by computing bottom SCCs starting at each top vertex. - * Note: each bottom SCC must contain a top vertex. - */ - + // Extract tangles by computing bottom SCCs starting at each top vertex. + // Note: each bottom SCC must contain a top vertex. std::fill(pea_vidx, pea_vidx+nodecount(), '\0'); pea_curidx = 1; - if (extractTangles(top, Z, str)) changes = true; } + } else { + (pl == 0 ? lowest_open_0 : lowest_open_1) = top; } Z.reset(); @@ -568,14 +568,10 @@ DFTLSolver::sptl(bitset &V, bitset &R, const int player, int &lowest_top) } if (closed_region) { - /** - * Extract tangles by computing bottom SCCs starting at each top vertex. - * Note: each bottom SCC must contain a top vertex. - */ - + // Extract tangles by computing bottom SCCs starting at each top vertex. + // Note: each bottom SCC must contain a top vertex. std::fill(pea_vidx, pea_vidx+nodecount(), '\0'); pea_curidx = 1; - if (extractTangles(top, Z, str)) changes = true; } @@ -732,13 +728,20 @@ DFTLSolver::run() pea_root.resize(nodecount()); // it turns out that doing DFTL for both players SIMULTANEOUSLY results in TC+ being a counterexample!! - /*if (!oneplayer) { + // this is the case if we remove the top of the lowest region if it is open because both players + // have the lowest region closed in every single step + // this is the case if we remove the top of the lowest region that is open as well + // therefore, there is no point in doing DFTL simultaneously for both players, the interleaved + // variant does not have this weakness +#if 0 + if (!oneplayer) { while (G.any()) { if (trace) logger << "\033[1;38;5;196miteration\033[m \033[1;36m" << iterations << "\033[m\n"; iterations++; if (!search(-1)) THROW_ERROR("solver stuck"); } - } else*/ + } else +#endif if (!oneplayer) { // Interleave solving for player Even and player Odd