// Hyperbolic Rogue -- regular honeycombs // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details /** \file reg3.cpp * \brief regular honeycombs * * works with spherical and hyperbolic ones -- Euclidean cubic tiling implemented in euclid.cpp * includes non-quotient spaces as well as field quotient and elliptic spaces * hyperbolic honeycombs rely on bt:: to deal with floating point errors (just like archimedean) */ #include "hyper.h" namespace hr { #if MAXMDIM >= 4 namespace binary { void build_tmatrix(); void virtualRebaseSimple(heptagon*& base, transmatrix& at); int celldistance3(heptagon *c1, heptagon *c2); hyperpoint deparabolic3(hyperpoint h); } /** \brief regular three-dimensional tessellations */ EX namespace reg3 { #if HDR inline short& altdist(heptagon *h) { return h->emeraldval; } #endif EX bool in() { if(fake::in()) return FPIU(in()); return GDIM == 3 && !euclid && !bt::in() && !nonisotropic && !hybri && !kite::in(); } EX void generate() { if(fake::in()) { fake::generate(); return; } int& loop = cgi.loop; int& face = cgi.face; auto& vertices_only = cgi.vertices_only; auto& spins = cgi.spins; auto& cellshape = cgi.cellshape; auto& adjcheck = cgi.adjcheck; auto& dirs_adjacent = cgi.dirs_adjacent; if(S7 == 4) face = 3; if(S7 == 6) face = 4; if(S7 == 12) face = 5; if(S7 == 8) face = 3; /* icosahedron not implemented */ loop = ginf[geometry].tiling_name[5] - '0'; DEBB(DF_GEOM, ("face = ", face, " loop = ", loop, " S7 = ", S7)); /* dual_angle : the angle between two face centers in the dual cell */ ld dual_angle = binsearch(0, M_PI, [&] (ld d) { hyperpoint h0 = cpush(0, 1) * C0; hyperpoint h1 = cspin(0, 1, d) * h0; hyperpoint h2 = cspin(1, 2, 2*M_PI/loop) * h1; return hdist(h0, h1) > hdist(h1, h2); }); /* angle_between_faces : the distance between two face centers of cells */ ld angle_between_faces = binsearch(0, M_PI, [&] (ld d) { hyperpoint h0 = cpush(0, 1) * C0; hyperpoint h1 = cspin(0, 1, d) * h0; hyperpoint h2 = cspin(1, 2, 2*M_PI/face) * h1; return hdist(h0, h1) > hdist(h1, h2); }); if(S7 == 8) { angle_between_faces = min(angle_between_faces, M_PI - angle_between_faces); /* 24-cell is a special case because it is the only one with '4' in the middle of the Schlaefli symbol. */ /* The computations above assume 3 */ hyperpoint h1 = hpxy3(.5,.5,.5); hyperpoint h2 = hpxy3(.5,.5,-.5); dual_angle = hdist(h1, h2); } DEBB(DF_GEOM, ("angle between faces = ", angle_between_faces)); DEBB(DF_GEOM, ("dual angle = ", dual_angle)); ld inp_length = binsearch(0, 1.55, [&] (ld d) { hyperpoint h = xpush(-d) * spin(2*M_PI/face) * xpush0(d); ld alpha = M_PI - atan2(-h[1], h[0]); return (alpha < dual_angle / 2) ? hyperbolic : sphere; }); DEBB(DF_GEOM, ("inp length = ", inp_length)); ld edge_length = hdist(xpush0(inp_length), spin(2*M_PI/face) * xpush0(inp_length)); if(S7 == 8) edge_length = hdist(normalize(hpxyz3(1,1,0,0)), normalize(hpxyz3(1,0,1,0))); DEBB(DF_GEOM, ("edge length = ", edge_length)); /* frontal face direction */ hyperpoint h0 = xtangent(1); /* three faces adjacent to frontal face direction */ hyperpoint h1 = cspin(0, 1, angle_between_faces) * h0; hyperpoint h2 = cspin(1, 2, 2*M_PI/face) * h1; hyperpoint h3 = cspin(1, 2, -2*M_PI/face) * h1; /* directions of vertices [h0,h1,h2] and [h0,h1,h3] */ hyperpoint dir_v2 = S7 == 8 ? (h1 + h2) : (h0 + h1 + h2); hyperpoint dir_v3 = S7 == 8 ? (h1 + h3) : (h0 + h1 + h3); DEBB(DF_GEOM, ("dir_v2 = ", dir_v2)); DEBB(DF_GEOM, ("dir_v3 = ", dir_v3)); dir_v2 = tangent_length(dir_v2, 1); dir_v3 = tangent_length(dir_v3, 1); DEBB(DF_GEOM, ("S7 = ", S7)); DEBB(DF_GEOM, ("dir_v2 = ", dir_v2)); DEBB(DF_GEOM, ("dir_v3 = ", dir_v3)); /* the distance from cell center to cell vertex */ ld vertex_distance; if(cgflags & qIDEAL) { vertex_distance = 13; } else { vertex_distance = binsearch(0, M_PI, [&] (ld d) { // sometimes breaks in elliptic dynamicval g(geometry, elliptic ? gCell120 : geometry); hyperpoint v2 = direct_exp(dir_v2 * d); hyperpoint v3 = direct_exp(dir_v3 * d); return hdist(v2, v3) >= edge_length; }); } DEBB(DF_GEOM, ("vertex_distance = ", vertex_distance)); /* actual vertex */ hyperpoint v2 = direct_exp(dir_v2 * vertex_distance); hyperpoint mid = Hypc; for(int i=0; imove(a)->c7, h2->c7)); println(hlog, "error in hrmap_quotient3:::relative_matrix"); return Id; } int encode_coord(const crystal::coord& co) { int c = 0; for(int i=0; i<4; i++) c |= ((co[i]>>1) & 3) << (2*i); return c; } EX crystal::coord decode_coord(int a) { crystal::coord co; for(int i=0; i<4; i++) co[i] = (a & 3) * 2, a >>= 2; return co; } struct hrmap_from_crystal : hrmap_quotient3 { hrmap_from_crystal() { initialize(256); if(1) { auto m = crystal::new_map(); dynamicval cm(currentmap, m); for(int a=0; a<256; a++) { auto co = decode_coord(a); heptagon *h1 = get_heptagon_at(co); for(int d=0; d<8; d++) { int b = encode_coord(crystal::get_coord(h1->cmove(d))); allh[a]->c.connect(d, allh[b], h1->c.spin(d), false); tmatrices[a].push_back(crystal::get_adj(h1, d)); } } delete m; } } }; struct hrmap_field3 : reg3::hrmap_quotient3 { fieldpattern::fpattern *f; hrmap_field3(fieldpattern::fpattern *ptr) { f = ptr; auto lgr = f->local_group; int N = isize(f->matrices) / lgr; initialize(N); vector moveid(S7), movedir(lgr); for(int s=0; sfullv[s] * cgi.adjmoves[0], cgi.adjmoves[i])) moveid[i] = s; for(int s=0; sfullv[s]) * cgi.adjmoves[0]), tC0(cgi.adjmoves[i])) < 1e-4) movedir[s] = i; for(int a=0; agmul(f->gmul(k, moveid[b]), lgr); for(int l=0; lgmul(k, l) % lgr == 0) { tmatrices[a][b] = cgi.adjmoves[b] * f->fullv[l]; allh[a]->c.connect(b, allh[k/lgr], movedir[l], false); } } } create_patterns(); } set plane; void make_plane(cellwalker cw) { if(plane.count(cw)) return; plane.insert(cw); for(int i=0; i crystal::c0) boundaries.insert(res); } periods.clear(); for(int index = 5; index >= 0; index--) { for(auto k: boundaries) println(hlog, k); DEBB(DF_GEOM, ("simplifying...")); for(auto by: boundaries) if(among(by[index], 1, -1)) { DEBB(DF_GEOM, ("simplifying by ", by)); periods.push_back(by); set nb; for(auto v: boundaries) if(v == by) ; else if(v[index] % by[index] == 0) nb.insert(v - by * (v[index] / by[index])); else println(hlog, "error"); boundaries = move(nb); break; } } } int get_rep(coord a) { a = a - periods[0] * (a[5] / periods[0][5]); a = a - periods[1] * (a[4] / periods[1][4]); a = a - periods[2] * (a[3] / periods[2][3]); for(int i=0; i<3; i++) a[i] = gmod(a[i], 5); return a[2] * 25 + a[1] * 5 + a[0]; } coord decode(int id) { coord res = crystal::c0; for(int a=0; a<3; a++) res[a] = id % 5, id /= 5; return res; } struct hrmap_singlecell : hrmap_quotient3 { hrmap_singlecell(ld angle) { initialize(1); tmatrices[0].resize(S7); for(int b=0; bc.connect(b, allh[0], (b+S7/2) % S7, false); transmatrix T = cgi.adjmoves[b]; hyperpoint p = tC0(T); tmatrices[0][b] = rspintox(p) * xpush(hdist0(p)) * cspin(2, 1, angle) * spintox(p); } } }; struct hrmap_seifert_cover : hrmap_quotient3 { hrmap_seifert_cover() { if(periods.empty()) build_reps(); initialize(125); for(int a=0; a<125; a++) { tmatrices[a].resize(12); for(int b=0; b<12; b++) { coord x = decode(a); if(b < 6) x[b]++; else x[b-6]--; int a1 = get_rep(x); allh[a]->c.connect(b, allh[a1], flip(b), false); transmatrix T = cgi.adjmoves[b]; hyperpoint p = tC0(T); tmatrices[a][b] = rspintox(p) * xpush(hdist0(p)) * cspin(2, 1, 108 * degree) * spintox(p); } } } }; } struct hrmap_reg3 : hrmap { heptagon *origin; hrmap *binary_map; hrmap_quotient3 *quotient_map; unordered_map> reg_gmatrix; unordered_map > > altmap; vector spherecells; vector& allcells() override { if(sphere) return spherecells; return hrmap::allcells(); } hrmap_reg3() { origin = tailored_alloc (S7); heptagon& h = *origin; h.s = hsOrigin; h.cdata = NULL; h.alt = NULL; h.distance = 0; h.fiftyval = 0; h.fieldval = 0; h.emeraldval = 0; h.c7 = newCell(S7, origin); if(sphere) spherecells.push_back(h.c7); worst_error1 = 0, worst_error2 = 0; dynamicval cr(currentmap, this); heptagon *alt = NULL; transmatrix T = Id; binary_map = nullptr; quotient_map = nullptr; #if CAP_FIELD if(geometry == gSpace344) { quotient_map = new hrmap_from_crystal; } else if(geometry == gSpace535) { quotient_map = new seifert_weber::hrmap_seifert_cover; } else if(hyperbolic) { quotient_map = new hrmap_field3(&currfp); } #endif h.zebraval = quotient_map ? quotient_map->allh[0]->zebraval : 0; if(hyperbolic) { dynamicval g(geometry, gBinary3); bt::build_tmatrix(); alt = tailored_alloc (S7); alt->s = hsOrigin; alt->emeraldval = 0; alt->zebraval = 0; alt->distance = 0; alt->alt = alt; alt->cdata = NULL; alt->c7 = NULL; binary_map = bt::new_alt_map(alt); T = xpush(.01241) * spin(1.4117) * xpush(0.1241) * cspin(0, 2, 1.1249) * xpush(0.07) * Id; } reg_gmatrix[origin] = make_pair(alt, T); altmap[alt].emplace_back(origin, T); celllister cl(origin->c7, 4, 100000, NULL); for(cell *c: cl.lst) { hyperpoint h = tC0(relative_matrix(c->master, origin, C0)); cgi.close_distances[bucketer(h)] = cl.getdist(c); } } ld worst_error1, worst_error2; heptagon *getOrigin() override { return origin; } void fix_distances(heptagon *h, heptagon *h2) { vector to_fix; auto fix_pair = [&] (heptagon *h, heptagon *h2) { if(!h2) return; if(h->distance > h2->distance+1) { h->distance = h2->distance + 1; to_fix.push_back(h); } else if(h2->distance > h->distance+1) { h2->distance = h->distance + 1; to_fix.push_back(h2); } if(h->alt && h->alt == h2->alt) { if(altdist(h) > altdist(h2) + 1) { altdist(h) = altdist(h2) + 1; to_fix.push_back(h); } else if (altdist(h2) > altdist(h) + 1) { altdist(h2) = altdist(h) + 1; to_fix.push_back(h2); } } }; if(!h2) to_fix = {h}; else fix_pair(h, h2); for(int i=0; imove(j)); } } #define DEB 0 heptagon *counterpart(heptagon *h) { return quotient_map->allh[h->fieldval]; } heptagon *create_step(heptagon *parent, int d) override { auto& p1 = reg_gmatrix[parent]; if(DEB) println(hlog, "creating step ", parent, ":", d, ", at ", p1.first, tC0(p1.second)); heptagon *alt = p1.first; #if CAP_FIELD transmatrix T = p1.second * (quotient_map ? quotient_map->tmatrices[parent->fieldval][d] : cgi.adjmoves[d]); #else transmatrix T = p1.second * cgi.adjmoves[d]; #endif transmatrix T1 = T; if(hyperbolic) { dynamicval g(geometry, gBinary3); dynamicval cm(currentmap, binary_map); binary_map->virtualRebase(alt, T); } fixmatrix(T); auto hT = tC0(T); bool hopf = stretch::applicable(); if(hopf) T = stretch::translate(hT); if(DEB) println(hlog, "searching at ", alt, ":", hT); if(DEB) for(auto& p2: altmap[alt]) println(hlog, "for ", tC0(p2.second), " intval is ", intval(tC0(p2.second), hT)); ld err; for(auto& p2: altmap[alt]) if((err = intval(tC0(p2.second), hT)) < 1e-3) { if(err > worst_error1) println(hlog, format("worst_error1 = %lg", double(worst_error1 = err))); // println(hlog, "YES found in ", isize(altmap[alt])); if(DEB) println(hlog, "-> found ", p2.first); int fb = 0; hyperpoint old = tC0(p1.second);; if(!hopf) T * (inverse(T1) * old); #if CAP_FIELD if(quotient_map) { p2.first->c.connect(counterpart(parent)->c.spin(d), parent, d, false); fix_distances(p2.first, parent); return p2.first; } #endif for(int d2=0; d2 worst_error2) println(hlog, format("worst_error2 = %lg", double(worst_error2 = err))); if(p2.first->move(d2)) println(hlog, "error: repeated edge"); p2.first->c.connect(d2, parent, d, false); fix_distances(p2.first, parent); fb++; } } if(fb != 1) { println(hlog, "found fb = ", fb); println(hlog, old); for(int d2=0; d2c.connect(d, parent, d, false); return parent; } return p2.first; } if(DEB) println(hlog, "-> not found"); int d2 = 0, fv = isize(reg_gmatrix); #if CAP_FIELD if(quotient_map) { auto cp = counterpart(parent); d2 = cp->c.spin(d); fv = cp->c.move(d)->fieldval; } #endif if(hopf) { hyperpoint old = tC0(p1.second); for(d2=0; d2 address; /** nles[x] lists the addresses from which we can reach address x * without ever ending in the starting point */ map> nonlooping_earlier_states; vector> possible_states; void find_mappings() { auto &nles = nonlooping_earlier_states; nles.clear(); vector
bfs; int qty = isize(quotient_map->allh); if(geometry == gSpace535) qty = 1; for(int i=0; iallh[fv]->move(d)->fieldval; }; int qstate = isize(children) / S7; DEBB(DF_GEOM, ("qstate = ", qstate)); for(int i=0; i= 0) { address next = {mov(fv, d), nstate}; if(!nles.count(next)) bfs.push_back(next); nles[next].insert(last); } } } vector q(qstate, 0); for(auto p: bfs) q[p.second]++; vector q2(isize(quotient_map->allh)+1, 0); for(auto p: q) q2[p]++; DEBB(DF_GEOM, ("q2 = ", q2)); bfs = {}; for(int i=0; i= 0) { address next = {mov(fv, d), nstate}; if(!nles.count(next)) continue; int c = isize(nles[next]); nles[next].erase(last); if(nles[next].empty() && c) { nles.erase(next); bfs.push_back(next); } } } } DEBB(DF_GEOM, ("removed cases = ", isize(bfs))); possible_states.resize(qstate); for(auto& p: nonlooping_earlier_states) possible_states[p.first.first].push_back(p.first.second); } hrmap_reg3_rule() : fp(0) { if(S7 == 6) load_ruleset("honeycomb-rules-435.dat"); else if(ginf[geometry].vertex == 5) load_ruleset("honeycomb-rules-535.dat"); else load_ruleset("honeycomb-rules-534.dat"); origin = tailored_alloc (S7); heptagon& h = *origin; h.s = hsOrigin; h.cdata = NULL; h.alt = NULL; h.distance = 0; h.zebraval = 0; h.fieldval = 0; h.fiftyval = root[0]; h.c7 = NULL; h.c7 = newCell(S7, origin); int opos = 0; for(int c: children) { if(c >= 0) otherpos.push_back(-1); else { otherpos.push_back(opos); while(other[opos] != ',') opos++; opos++; } } quotient_map = nullptr; if(geometry == gSpace535) quotient_map = new seifert_weber::hrmap_seifert_cover(); else quotient_map = new hrmap_field3(&fp); if(geometry == gSpace535) emerald_map = new seifert_weber::hrmap_seifert_cover(); else emerald_map = new hrmap_field3(&currfp); h.emeraldval = 0; find_mappings(); } heptagon *getOrigin() override { return origin; } #define DEB 0 heptagon *counterpart(heptagon *h) { return quotient_map->allh[h->fieldval]; } vector evmemo; void find_emeraldval(heptagon *target, heptagon *parent, int d) { if(geometry == gSpace535) { target->emeraldval = target->fieldval; target->zebraval = 0; return; } auto& cr = cgi.cellrotations; if(evmemo.empty()) { println(hlog, "starting"); map matrix_hashtable; auto matrix_hash = [] (const transmatrix& M) { return bucketer(M[0][0]) + bucketer(M[0][1]) * 71 + bucketer(M[0][2]) * 113 + bucketer(M[1][0]) * 1301 + bucketer(M[1][1]) * 1703 + bucketer(M[1][2]) * 17031 + bucketer(M[2][2]) * 2307 + bucketer(M[2][0]) * 2311 + bucketer(M[2][1]) * 10311; }; for(int i=0; iemeraldval; memo_id = memo_id * isize(quotient_map->allh) + parent->fieldval; memo_id = memo_id * S7 + d; target->emeraldval = evmemo[memo_id]; target->zebraval = emerald_map->allh[target->emeraldval / isize(cr)]->zebraval; } heptagon *create_step(heptagon *parent, int d) override { int id = parent->fiftyval; auto cp = counterpart(parent); int d2 = cp->c.spin(d); int fv = cp->c.move(d)->fieldval; // indenter ind(2); heptagon *res = nullptr; int id1 = children[S7*id+d]; int pos = otherpos[S7*id+d]; // println(hlog, "id=", id, " d=", d, " d2=", d2, " id1=", id1, " pos=", pos); if(id1 != -1) { res = tailored_alloc (S7); if(parent->c7) res->c7 = newCell(S7, res); else res->c7 = nullptr; res->alt = nullptr; res->cdata = nullptr; res->fieldval = fv; res->distance = parent->distance + 1; res->fiftyval = id1; find_emeraldval(res, parent, d); // res->c.connect(d2, parent, d, false); } else if(other[pos] == ('A' + d) && other[pos+1] == ',') { res = tailored_alloc (S7); res->c7 = nullptr; res->alt = parent->alt; res->cdata = nullptr; res->fieldval = fv; res->distance = parent->distance - 1; vector possible; int pfv = parent->fieldval; if(geometry == gSpace535) pfv = 0; for(auto s: nonlooping_earlier_states[address{pfv, id}]) possible.push_back(s.second); id1 = hrand_elt(possible, 0); res->fiftyval = id1; find_emeraldval(res, parent, d); } else { heptagon *at = parent; while(other[pos] != ',') { int dir = (other[pos++] & 31) - 1; // println(hlog, "from ", at, " go dir ", dir); at = at->cmove(dir); } res = at; } if(!res) throw "res missing"; if(res->move(d2)) println(hlog, "res conflict"); res->c.connect(d2, parent, d, false); return res; } ~hrmap_reg3_rule() { if(quotient_map) delete quotient_map; clearfrom(origin); } void draw() override { sphereflip = Id; // for(int i=0; imaster, cview()); while(!dq::drawqueue.empty()) { auto& p = dq::drawqueue.front(); heptagon *h = get<0>(p); transmatrix V = get<1>(p); dynamicval b(band_shift, get<2>(p)); bandfixer bf(V); dq::drawqueue.pop(); cell *c = h->c7; if(!do_draw(c, V)) continue; drawcell(c, V); if(in_wallopt() && isWall3(c) && isize(dq::drawqueue) > 1000) continue; for(int i=0; imove(i)) { dq::enqueue(h->move(i), V * adj(h, i)); } } } transmatrix adj(heptagon *h, int d) override { return quotient_map->adj(h, d); } transmatrix relative_matrix(heptagon *h2, heptagon *h1, const hyperpoint& hint) override { return relative_matrix_recursive(h2, h1); } vector get_vertices(cell* c) override { return cgi.vertices_only; } }; struct hrmap_reg3_rule_alt : hrmap { heptagon *origin; hrmap_reg3_rule_alt(heptagon *o) { origin = o; } }; EX hrmap *new_alt_map(heptagon *o) { return new hrmap_reg3_rule_alt(o); } EX void link_structures(heptagon *h, heptagon *alt, hstate firststate) { auto cm = (hrmap_reg3_rule*) currentmap; alt->fieldval = h->fieldval; if(geometry == gSpace535) alt->fieldval = 0; if(firststate == hsOrigin) { alt->fiftyval = cm->root[alt->fieldval]; return; } vector& choices = cm->possible_states[alt->fieldval]; vector choices2; for(auto c: choices) { bool ok = true; for(int d=0; d<12; d++) if(h->cmove(d)->distance < h->distance) if(cm->children[S7*c+d] == -1) ok = false; if(ok) choices2.push_back(c); } alt->fiftyval = hrand_elt(choices2, -1); } EX bool reg3_rule_available = true; EX bool in_rule() { return reg3_rule_available && among(geometry, gSpace534, gSpace435, gSpace535); } EX hrmap* new_map() { if(geometry == gSeifertCover) return new seifert_weber::hrmap_seifert_cover; if(geometry == gSeifertWeber) return new seifert_weber::hrmap_singlecell(108*degree); if(geometry == gHomologySphere) return new seifert_weber::hrmap_singlecell(36*degree); if(quotient && !sphere) return new hrmap_field3(&currfp); if(in_rule()) return new hrmap_reg3_rule; return new hrmap_reg3; } hrmap_reg3* regmap() { return ((hrmap_reg3*) currentmap); } EX int quotient_count() { return isize(regmap()->quotient_map->allh); } /** This is a generalization of hyperbolic_celldistance in expansion.cpp to three dimensions. It still assumes that there are at most 4 cells around every edge, and that distances from the origin are known, so it works only in {5,3,4}. */ int celldistance_534(cell *c1, cell *c2) { int d1 = celldist(c1); int d2 = celldist(c2); vector s1 = {c1}; vector s2 = {c2}; int best = 99999999; int d0 = 0; auto go_nearer = [&] (vector& v, int& d) { vector w; for(cell *c: v) forCellEx(c1, c) if(celldist(c1) < d) w.push_back(c1); sort(w.begin(), w.end()); d--; d0++; auto last = std::unique(w.begin(), w.end()); w.erase(last, w.end()); v = w; }; while(d0 < best) { for(cell *a1: s1) for(cell *a2: s2) { if(a1 == a2) best = min(best, d0); else if(isNeighbor(a1, a2)) best = min(best, d0+1); } if(d1 == 0 && d2 == 0) break; if(d1 >= d2) go_nearer(s1, d1); if(d1 < d2) go_nearer(s2, d2); } return best; } EX int celldistance(cell *c1, cell *c2) { if(c1 == c2) return 0; if(c1 == currentmap->gamestart()) return c2->master->distance; if(c2 == currentmap->gamestart()) return c1->master->distance; if(geometry == gSpace534) return celldistance_534(c1, c2); auto r = regmap(); hyperpoint h = tC0(r->relative_matrix(c1->master, c2->master, C0)); int b = bucketer(h); if(cgi.close_distances.count(b)) return cgi.close_distances[b]; if(in_rule()) return clueless_celldistance(c1, c2); dynamicval g(geometry, gBinary3); return 20 + bt::celldistance3(r->reg_gmatrix[c1->master].first, r->reg_gmatrix[c2->master].first); } EX bool pseudohept(cell *c) { auto m = regmap(); if(cgflags & qSINGLE) return true; if(fake::in()) return FPIU(reg3::pseudohept(c)); if(sphere) { hyperpoint h = tC0(m->relative_matrix(c->master, regmap()->origin, C0)); if(S7 == 12) { hyperpoint h1 = cspin(0, 1, atan2(16, 69) + M_PI/4) * h; for(int i=0; i<4; i++) if(abs(abs(h1[i]) - .5) > .01) return false; return true; } if(S7 == 8) return h[3] >= .99 || h[3] <= -.99 || abs(h[3]) < .01; if(cgi.loop == 3 && cgi.face == 3 && S7 == 4) return c == m->gamestart(); if(cgi.loop == 4 && cgi.face == 3) return abs(h[3]) > .9; if(cgi.loop == 3 && cgi.face == 4) return abs(h[3]) > .9; if(cgi.loop == 5 && cgi.face == 3) return abs(h[3]) > .99 || abs(h[0]) > .99 || abs(h[1]) > .99 || abs(h[2]) > .99; } // chessboard pattern in 534 if(geometry == gField534) return hr::celldistance(c, currentmap->gamestart()) & 1; if(geometry == gCrystal344 || geometry == gCrystal534 || geometry == gSeifertCover) return false; if(quotient) return false; /* added */ auto mr = dynamic_cast (currentmap); if(mr) { if(geometry == gSpace535) return c->master->fieldval % 31 == 0; return c->master->fieldval == 0; } if(m && hyperbolic) { heptagon *h = m->reg_gmatrix[c->master].first; return (h->zebraval == 1) && (h->distance & 1); } return false; } EX void generate_cellrotations() { auto &cr = cgi.cellrotations; if(isize(cr)) return; for(int a=0; a 0.01) continue; vector perm(S7); for(int x=0; xreg_gmatrix[c->master].second); h = bt::deparabolic3(h); return regmap()->reg_gmatrix[c->master].first->distance * log(2) - h[0]; } unordered_map, int> memo; bool cdd; int celldistance(cell *c1, cell *c2) { if(memo.count(make_pair(c1, c2))) return memo[make_pair(c1, c2)]; if(c1 == c2) return 0; vector v[2]; v[0].push_back(c1); v[1].push_back(c2); int steps = 0; map visited; visited[c1] = 1; visited[c2] = 2; while(true) { if(cdd) { println(hlog, "state ", steps, "/",isize(v[0]), "/", isize(v[1])); println(hlog, " A: ", v[0]); println(hlog, " B: ", v[1]); } for(int i: {0,1}) { vector new_v; for(cell *c: v[i]) forCellCM(cn, c) if(adistance(cn) < adistance(c)) { auto &vi = visited[cn]; if((vi&3) == 0) { vi = 4 * (steps+1); vi |= (1<> ca1, ca2; int b1 = 4*steps-4; int b2 = ((vi>>2)<<2) - 4; for(auto p: visited) { if(cdd) println(hlog, p); int ps = p.second & 3; if(ps == 1+i && p.second >= b1) ca1.emplace_back(p.first, p.second/4); if(ps == 2-i && p.second >= b2 && p.second <= b2+8) ca2.emplace_back(p.first, p.second/4); } int bound = 1<<16; for(auto p1: ca1) for(auto p2: ca2) { hyperpoint h = tC0(relative_matrix(p1.first->master, p2.first->master)); int b = bucketer(h); if(close_distances.count(b)) { int d = close_distances[b] + p1.second + p2.second; if(cdd) println(hlog, "candidate: close=", close_distances[b], p1, p2, "; h = ", h); if(d < bound) bound = d; } else if(cdd) println(hlog, "bucket missing"); } return memo[make_pair(c1, c2)] = bound; return bound; } } v[i] = std::move(new_v); } steps++; } } cellwalker target; int tsteps; int dist_alt(cell *c) { if(!target.at) { target = cellwalker(currentmap->gamestart(), 0); tsteps = 0; for(int i=0; i<30; i++) target += wstep, target += rev, tsteps++; } if(specialland == laCamelot) return reg3::celldistance(c, target.at); else { int d = reg3::celldistance(c, target.at) - tsteps; if(d < 10) target += wstep, target += rev, tsteps++; return d; } } #endif // Construct a cellwalker in direction j from cw.at, such that its direction is as close // as possible to cw.spin. Assume that j and cw.spin are adjacent #if MAXMDIM >= 4 EX cellwalker strafe(cellwalker cw, int j) { hyperpoint hfront = tC0(cgi.adjmoves[cw.spin]); cw.at->cmove(j); transmatrix T = currentmap->adj(cw.at, j); for(int i=0; ic.spin(j)) if(hdist(hfront, T * tC0(cgi.adjmoves[i])) < cgi.strafedist + .01) return cellwalker(cw.at->cmove(j), i); println(hlog, "incorrect strafe"); exit(1); } EX int matrix_order(const transmatrix A) { transmatrix T = A; int res = 1; while(!eqmatrix(T, Id)) { res++; T = T * A; } return res; } EX void generate_fulls() { reg3::generate_cellrotations(); auto cons = [&] (int i0, int i1, int i2) { transmatrix T = build_matrix(cgi.adjmoves[ 0]*C0, cgi.adjmoves[ 1]*C0, cgi.adjmoves[ 2]*C0, C0); transmatrix U = build_matrix(cgi.adjmoves[i0]*C0, cgi.adjmoves[i1]*C0, cgi.adjmoves[i2]*C0, C0); return U * inverse(T); }; cgi.full_P = cgi.adjmoves[0]; cgi.full_R = S7 == 8 ? cons(1, 7, 0) : cons(1, 2, 0); cgi.full_X = S7 == 8 ? cons(1, 0, 6) : S7 == 6 ? cons(1, 0, 5) : cons(1, 0, cgi.face); cgi.xp_order = matrix_order(cgi.full_X * cgi.full_P); cgi.r_order = matrix_order(cgi.full_R); cgi.rx_order = matrix_order(cgi.full_R * cgi.full_X); println(hlog, "orders = ", tie(cgi.rx_order, cgi.r_order, cgi.xp_order)); } EX void construct_relations() { auto& rels = cgi.rels; if(!rels.empty()) return; rels.clear(); reg3::generate_cellrotations(); reg3::generate_fulls(); vector all; vector formulas; formulas.push_back(""); all.push_back(Id); hyperpoint v = cgi.cellshape[0]; auto add = [&] (transmatrix T) { for(int i=0; i 5) return; for(hyperpoint h: cgi.cellshape) if(hdist(T * h, v) < 1e-4) goto ok; return; ok: int id = add(T); // println(hlog, p, " x ", (s0+c), " = ", id); if(id >= isize(formulas)) formulas.push_back(formulas[p] + c); else if(id == 0) println(hlog, "reached identity: ", formulas[p]+c); else if(formulas[p][0] != formulas[id][0]) rels.emplace_back(formulas[p] + c, formulas[id]); }; for(int i=0; i