/** Honeycomb data generator. See rulegen.sh This algorithm works as follows: - We use a DFS-like algorithm to identify all the required states. To tell whether two cells c1 and c2 are in the same state, we compute its generate_ext_nei -- the same generate_ext_nei is the same state. To compute generate_ext_nei(c), we list all cells vertex-adjacent to c, and for each c' in this list, we compute FV(c')-FV(c), where FV is the distance from some central tile. It is crucial to identify the directions in unique way (in 2D we can simply use clockwise order, in 3D it is more difficult) -- we do this by using a regular pattern (see get_id). After all states are identified, we construct the tree of states -- every non-root state is attached to the first neighbor (according to the direction order) which has smaller FV. For non-tree directions, we construct a path going through nodes with smaller values of FV -- this guarantees termination of the algorithm in amortized time O(1). */ #include "zlib.h" #include "../hyper.h" namespace hr { int exh; map > rules; /** \brief distance from the center */ #define FV master->fiftyval /** \brief change i into a string containing a displayable character */ auto dis = [] (int i, char init='a') { return s0 + char(init + i); }; bool optimize_344 = false; /** \brief we use a regular pattern to make sure that the directions are identified consistently. In {5,3,5} we can just use the Seifert-Weber space for this identification; otherwise, we use the field pattern. */ int get_id(cell *c) { if(geometry == gSpace535) return 0; if(optimize_344 && geometry == gSpace344) { /* we use the 'pattern from crystal' */ /* but it is mod 4, mod 2 is enough for us */ int res = 0; int fv = c->master->fieldval; for(int i=0; i<4; i++) { res = 2 * res + (fv&1); fv >>= 2; } return res; } return c->master->fieldval; } /** \brief aux function for find_path which limits path length * the rule is that we make some moves which decrease FV, then we make some moves which increase FV */ string find_path(cell *x, cell *y, int steps) { if(x->FV != y->FV) { println(hlog, x, y, " steps=", steps, " d=", x->FV, " vs ", y->FV); exit(3); } if(x == y) return ""; if(steps == 0) return "?"; for(int i=0; imove(i) && x->move(i)->FV < x->FV) for(int j=0; jmove(j) && y->move(j)->FV < y->FV) { string ch = find_path(x->move(i), y->move(j), steps-1); if(ch == "?") continue; return dis(i) + ch + dis(y->c.spin(j)); } return "?"; } /** \brief aux function for find_path which limits path length * the rule is that we keep to a fixed FV-level (this works better in {x,x,3}) */ string find_path_side(cell *x, cell *y, int steps) { if(x->FV != y->FV) { println(hlog, x, y, " steps=", steps, " d=", x->FV, " vs ", y->FV); exit(3); } if(x == y) return ""; if(steps == 0) return "?"; for(int i=0; imove(i) && x->move(i)->FV == x->FV) { string ch = find_path_side(x->move(i), y, steps-1); if(ch == "?") continue; return dis(i) + ch; } return "?"; } /** \brief find the sequence of moves we need to take to get from y to x (x and y must be the same fv-level) * return '?' if nothing found */ string find_path(cell *x, cell *y) { if(x == y) return ""; if(geometry == gSpace353) { static int max_steps = -1; for(int steps=0; steps<5; steps++) { string f = find_path_side(x, y, steps); if(f != "?") { if(steps > max_steps) { println(hlog, "found a sidepath with ", max_steps = steps, " steps"); } return f; } } if(max_steps < 10) { max_steps = 10; println(hlog, "failed to find_path_side"); } } for(int steps=0;; steps++) { string f = find_path(x, y, steps); if(f != "?") return f; } } /** a map of all the cells vertex-adjacent to c */ struct ext_nei_rules_t { vector from, dir, original; }; /** ext_nei_rules_t need to be created only once for each get_id */ map ext_nei_rules; /** aux recursive function of construct_rules: the compact variant */ void listnear_compact(cell *c, ext_nei_rules_t& e, const transmatrix& T, int id, set& visi) { visi.insert(c); int a = 0, b = 0; for(int i=0; iadj(c, i); for(auto v: cgi.vertices_only) for(auto w: cgi.vertices_only) if(hdist(v, U*w) < 1e-3) ok = true; if(!ok) continue; cell *c1 = c->cmove(i); int id1 = isize(e.from); e.from.push_back(id); e.dir.push_back(i); a++; e.original.push_back(!visi.count(c1)); if(e.original.back()) { b++; listnear_compact(c1, e, U, id1, visi); } } } /** aux recursive function of construct_rules: the maxdist variant */ void listnear_exh(cell *c, ext_nei_rules_t& e, int maxdist) { map dist; map origdir; vector lst; println(hlog, "called listnear_exh for: ", c); auto enqueue = [&] (cell *c, int d, int od) { if(dist.count(c)) return; dist[c] = d; origdir[c] = od; lst.push_back(c); }; enqueue(c, 0, -1); for(int k=0; k= 0 && !cgi.dirs_adjacent[i][odi]) continue; cell *c1 = ca->cmove(i); e.from.push_back(k); e.dir.push_back(i); e.original.push_back(!dist.count(c1)); if(e.original.back() && di < maxdist) enqueue(c1, di, ca->c.spin(i)); } } } /** \brief create ext_nei_rules_t for the given c */ void construct_rules(cell *c, ext_nei_rules_t& e) { e.from = {-1}; e.dir = {-1}; e.original = {1}; if(!exh) { set visi; listnear_compact(c, e, Id, 0, visi); } else { listnear_exh(c, e, exh); } int orgc = 0; for(auto i: e.original) orgc += i; println(hlog, "id ", get_id(c), " list length = ", isize(e.original), " original = ", orgc); } /** \brief we learn that a and b are connected -- make sure that their FV's match */ void fix_dist(cell *a, cell *b) { if(a->FV > b->FV+1) { a->FV = b->FV+1; forCellEx(c, a) fix_dist(a, c); } if(b->FV > a->FV+1) { b->FV = a->FV+1; forCellEx(c, b) fix_dist(b, c); } } /** \brief compute ext_nei_rules_t for the given cell, and make it into a string form; also do fix_dist */ string generate_ext_nei(cell *c) { int fv = get_id(c); auto& e = ext_nei_rules[fv]; if(e.from.empty()) construct_rules(c, e); vector ext_nei = {c}; for(int i=1; icmove(e.dir[i]); fix_dist(last, next); ext_nei.push_back(next); } string res; for(int i=0; iFV - c->FV); return its(fv) + ":" + res; } /** cells become 'candidates' before their generate_ext_nei is checked in order to let them become states */ set candidates; vector candidates_list; /** the state ID for a given string returned by generate_ext_nei */ map id_of; /** cell representing the given state ID */ vector rep_of; /** current number of states */ int number_states = 0; /** \brief for state s, child_rules[s][i] is -1 if i-th neighbor not a child; otherwise, the state index of that neighbor */ vector > child_rules; /** parent direction for every state */ vector parent_list; /** \brief if child_rules[s][i] is -1, the rules to get to that neighbor */ vector > side_rules; void add_candidate(cell *c) { if(candidates.count(c)) return; candidates.insert(c); candidates_list.push_back(c); } /** the main function */ void test_canonical(string fname) { stop_game(); reg3::reg3_rule_available = false; fieldpattern::use_rule_fp = true; fieldpattern::use_quotient_fp = true; start_game(); int qc = reg3::quotient_count(); vector c0; if(optimize_344 && geometry == gSpace344) qc = 16; /* we start from a 'center' in every get_id-type */ if(geometry == gSpace535) { c0.resize(qc, cwt.at); } else { for(int fv=0; fvcmove(hrand(S7)); c->FV = 0; c0.push_back(c); } } for(cell* c: c0) add_candidate(c); vector empty(S7); for(auto& e: empty) e = -1; println(hlog, "empty = ", empty); /** generate candidate_list using a BFS-like algorithm, starting from c0 */ for(int i=0; icmove(i)); } } child_rules.resize(number_states, empty); parent_list.resize(number_states); println(hlog, "found ", its(number_states), " states"); /** generate child_rules */ for(int i=0; imove(a); if(c1->FV < c->FV) parent_list[i] = a; if(c1->FV <= c->FV) continue; for(int b=0; bmove(b); if(c2->FV != c->FV) continue; if(c2 == c) { string st = generate_ext_nei(c1); if(!id_of.count(st)) { println(hlog, "error: new state generated while generating child_rules"); } child_rules[i][a] = id_of[st]; } break; } continue; } } if(true) { /* minimize the automaton */ // println(hlog, "original rules: ", child_rules); fflush(stdout); vector ih(number_states, 0); int lqids = 0; for(int a=0; a<100; a++) { set> found; vector> v(number_states); map, int> ids; for(int i=0; i res(S7+1); for(int d=0; dmove(a); if(!c1) continue; if(c1->FV < c->FV && !cpar) cpar = c1, a0 = a; } for(int a=0; amove(a); if(!c1) continue; bool is_child = false; cell* c2 = nullptr; int dir = 0; if(c1->FV >= c->FV) { for(int b=0; bmove(b); if(!c2) continue; if(c2->FV >= c1->FV) continue; dir = c1->c.spin(b); break; } } is_child = (c2 == c); bool was_child = child_rules[id][a] >= 0; if(is_child ^ was_child) { println(hlog, "id=", id, " a=", a); println(hlog, "is_child = ", is_child); println(hlog, "was_child = ", was_child); println(hlog, "c fv = ", c->FV); println(hlog, "c1 fv = ", c1->FV, " [", a, "]"); if(c2 == nullptr) { println(hlog, "c2 missing"); } else println(hlog, "c2 fv = ", c2->FV, " [", c2->c.spin(dir), "]"); println(hlog, c, "->", c1, "->", c2); fflush(stdout); cell *r = rep_of[id]; println(hlog, r, " at ", r->FV); cell *r1 = r->move(a); if(!r1) { println(hlog, "error: r1 missing"); continue; } println(hlog, r1, " at ", r1->FV); for(int a=0; amove(a)) println(hlog, a, ":", r1->move(a), " at ", r1->move(a)->FV); fflush(stdout); exit(3); } if(is_child) continue; string solu; if(c1->FV < c->FV) solu = dis(a0, 'A') + find_path(cpar, c1); else if(c1->FV == c->FV) solu = dis(a0, 'A') + find_path(cpar, c2) + dis(dir); else solu = find_path(c, c2) + dis(dir); auto& sr = side_rules[id][a]; if(sr != "" && sr != solu) { println(hlog, "conflict: ", solu, " vs ", sr, " FV = ", c->FV, " vs ", c1->FV); if(isize(sr) < isize(solu)) continue; } sr = solu; continue; } } // println(hlog, side_rules); string side_data; for(auto& a: side_rules) for(auto&b :a) if(b != "") side_data += b + ","; println(hlog, side_data); vector data; for(auto& a: child_rules) for(auto i:a) data.push_back(i); shstream ss; auto& fp = currfp; hwrite_fpattern(ss, fp); vector root(qc, 0); for(int i=0; i