diff --git a/devmods/honeycomb-rule-generator.cpp b/devmods/honeycomb-rule-generator.cpp index e95da3ed..6b9afb67 100644 --- a/devmods/honeycomb-rule-generator.cpp +++ b/devmods/honeycomb-rule-generator.cpp @@ -7,11 +7,27 @@ Usage: ./hyper -geo 534h -gen-rule honeycomb-rules-534.dat -quit ./hyper -geo 535h -gen-rule honeycomb-rules-535.dat -quit ./hyper -geo 435h -gen-rule honeycomb-rules-435.dat -quit +./hyper -geo 353h -gen-rule honeycomb-rules-353.dat -quit You need to change the value of XS7 to 6 (for 435) or 12 (for others) You also need to select 'fp used for rules' +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 dist 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" @@ -21,16 +37,28 @@ namespace hr { map > rules; -#define XS7 12 +/** \brief S7 -- for efficiency this is a fixed constant */ +#define XS7 20 + +/** \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); }; +/** \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; 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); @@ -49,8 +77,51 @@ string find_path(cell *x, cell *y, int steps) { 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); @@ -60,21 +131,22 @@ string find_path(cell *x, cell *y) { vector> rule_list; +/** 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; -set visi; - -void listnear(cell *c, ext_nei_rules_t& e, const transmatrix& T, int id) { +/** aux recursive function of construct_rules */ +void listnear(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: reg3::vertices_only) for(auto w: reg3::vertices_only) + 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); @@ -85,22 +157,24 @@ void listnear(cell *c, ext_nei_rules_t& e, const transmatrix& T, int id) { e.original.push_back(!visi.count(c1)); if(e.original.back()) { b++; - listnear(c1, e, U, id1); + listnear(c1, e, U, id1, visi); } } } +/** \brief create ext_nei_rules_t for the given c */ void construct_rules(cell *c, ext_nei_rules_t& e) { - visi.clear(); e.from = {-1}; e.dir = {-1}; e.original = {1}; - listnear(c, e, Id, 0); + set visi; + listnear(c, e, Id, 0, visi); 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; @@ -112,6 +186,8 @@ void fix_dist(cell *a, cell *b) { } } +/** \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]; @@ -128,15 +204,23 @@ string generate_ext_nei(cell *c) { 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; +/** \brief if child_rules[s][i] is -1, the rules to get to that neighbor */ vector > side_rules; void add_candidate(cell *c) { @@ -145,6 +229,7 @@ void add_candidate(cell *c) { candidates_list.push_back(c); } +/** the main function */ void test_canonical(string fname) { if(S7 != XS7) { println(hlog, "fix XS7=", S7); exit(4); } stop_game(); @@ -155,24 +240,28 @@ void test_canonical(string fname) { vector c0; + /* we start from a 'center' in every get_id-type */ if(geometry == gSpace535) { c0.resize(qc, cwt.at); } else { for(int fv=0; fvmaster->fieldval != fv; i++) c = c->cmove(hrand(S7)); + /* 100 to ensure that the FV-spheres around candidates do not interact */ + for(int i=0; i<100 || get_id(c) != fv; i++) c = c->cmove(hrand(S7)); c->FV = 0; c0.push_back(c); } } for(cell* c: c0) add_candidate(c); - + array empty; 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; imove(a); if(c1->FV <= c->FV) continue; @@ -198,17 +294,23 @@ void test_canonical(string fname) { cell *c2 = c1->move(b); if(c2->FV != c->FV) continue; if(c2 == c) { - child_rules[i][a] = id_of[generate_ext_nei(c1)]; + 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) { - println(hlog, "original rules: ", child_rules); + if(true) { + + /* minimize the automaton */ + + // println(hlog, "original rules: ", child_rules); fflush(stdout); vector ih(number_states, 0); @@ -227,14 +329,14 @@ void test_canonical(string fname) { } int qids = 0; for(auto hash: found) ids[hash] = qids++; - println(hlog, "qids = ", qids); + println(hlog, "minimization step: ", qids, " states"); if(qids == lqids) break; lqids = qids; for(int i=0; iFV); cell *r1 = r->move(a); - if(!r1) { println(hlog, "r1 missing"); continue; } + 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); @@ -326,7 +430,7 @@ void test_canonical(string fname) { auto& sr = side_rules[id][a]; if(sr != "" && sr != solu) { - println(hlog, "conflict: ", solu, " vs ", sr); + println(hlog, "conflict: ", solu, " vs ", sr, " FV = ", c->FV, " vs ", c1->FV); if(isize(sr) < isize(solu)) continue; } @@ -335,8 +439,8 @@ void test_canonical(string fname) { continue; } } - - println(hlog, side_rules); + + // println(hlog, side_rules); string side_data; for(auto& a: side_rules) for(auto&b :a) if(b != "") side_data += b + ",";