From 2fb92ecc2d9aa1220fc65a7c016f7d07c47d09e4 Mon Sep 17 00:00:00 2001 From: Zeno Rogue Date: Mon, 6 Oct 2025 21:13:03 +0200 Subject: [PATCH] arbiquotient --- arbiquotient.cpp | 413 +++++++++++++++++++++++++++++++++++++++++++++++ geom-exp.cpp | 18 ++- history.cpp | 2 +- hyper.cpp | 1 + 4 files changed, 429 insertions(+), 5 deletions(-) create mode 100644 arbiquotient.cpp diff --git a/arbiquotient.cpp b/arbiquotient.cpp new file mode 100644 index 00000000..72f37928 --- /dev/null +++ b/arbiquotient.cpp @@ -0,0 +1,413 @@ +#include "hyper.h" +#include +#include +#include + +namespace hr { + +EX namespace arbiquotient { + +map aqs; + +int next_id; + +struct aqdata { + cell *where; + bool closed; + cellwalker parent; + int size; + int id; + aqdata(cell *c) : where(c), parent(c) { id = next_id++; size = 1; closed = false; } + }; + +/* the stack of known unifications */ +vector> unifications; + +vector allaq; + +map aq; + +struct hr_precision_error : hr_exception { hr_precision_error() : hr_exception("precision error") {} }; + +struct aq_overflow : hr_exception { aq_overflow(): hr_exception("autoquotient overflow") {} }; +struct aq_unsupported : hr_exception { aq_unsupported(): hr_exception("unsupported orbifold") {} }; + +cellwalker rezero(cellwalker cw, cellwalker base) { + if(base.mirrored) cw += wmirror; cw -= base.spin; + return cw; + } + +cellwalker dezero(cellwalker cw, cellwalker base) { + cw += base.spin; if(base.mirrored) cw += wmirror; + return cw; + } + +cellwalker ufind(cellwalker cw) { + auto& d = aq.at(cw.at); + if(d.where != cw.at) throw hr_exception("where incorrect"); + if(d.parent == cw.at) return cw; + auto f = ufind(d.parent); + if(f.at != d.parent.at) + d.parent = f; + return dezero(f, cw); + } + +void enlist(cell *c) { + aq.emplace(c, c); + aqdata& ref = aq.at(c); + allaq.push_back(&ref); + } + +void default_list() { + cellwalker cw0 = currentmap->gamestart(); + aqs.clear(); + auto cw1 = cw0; for(int i=0; i<10; i++) cw1 = cw1 + wstep + 2; + auto cw2 = cw0+1; for(int i=0; i<10; i++) cw2 = cw2 + wstep + 2; cw2 = cw2 - 1; + aqs[cw1.at] = cw1; + aqs[cw2.at] = cw2; + } + +void clear_all() { + unifications = {}; next_id = 0; aq.clear(); allaq.clear(); + } + +bool apply_uni() { + while(unifications.size()) { + auto cwp = unifications.back(); + auto cw1 = cwp.first, cw2 = cwp.second; + unifications.pop_back(); + cw1 = ufind(cw1); + cw2 = ufind(cw2); + if(cw1.at == cw2.at) { + if(cw1 != cw2) return false; + continue; + } + auto d1 = &aq.at(cw1.at); + auto d2 = &aq.at(cw2.at); + if(d1->id > d2->id) { swap(cw1, cw2); swap(d1, d2); } + d1->size += d2->size; + d2->parent = rezero(cw1, cw2); + if(cw2.at->item == itGold) cw2.at->item = itRuby; + for(int i=0; itype; i++) { + auto m1 = cw1 + i + wstep; + auto m2 = cw2 + i + wstep; + if(aq.count(m1.at) && aq.count(m2.at)) + unifications.emplace_back(m1, m2); + } + } + return true; + } + +vector quotient_output(int dir) { + vector output; + int next_offset = 0; + map> offsets; + vector listorder; + auto assign_id = [&] (cellwalker cw) { + if(!offsets.count(cw.at)) { + auto& sh = arb::current.shapes[shvid(cw.at)]; + while(cw.spin % sh.cycle_length) cw--; + offsets[cw.at] = { next_offset, cw }; + listorder.push_back(cw.at); + next_offset += cw.at->type; + } + // auto [ofs, cw1] = offsets[cw.at]; + auto &tmp = offsets[cw.at]; + auto ofs = tmp.first; + auto cw1 = tmp.second; + return ofs + gmod(cw.spin - cw1.spin, cw.at->type); + }; + assign_id(cellwalker(currentmap->gamestart(), dir)); + for(int i=0; itype; j++) + output.push_back(assign_id(ufind(cw + j + wstep))); + } + return output; + } + +set seen_outputs; + +struct qdata { + string name; + vector connections; + }; + +vector all_found; + +void create() { + clear_all(); + + cellwalker cw0 = currentmap->gamestart(); + + enlist(cw0.at); + + int loop_id = 0; + + while(true) { + + if(!apply_uni()) throw aq_unsupported(); + + if(loop_id == isize(allaq)) break; + + cell *c = allaq[loop_id++]->where; + cellwalker cw(c); + auto cw1 = ufind(cw); + if(cw1 != cw) continue; + c->item = itGold; + + for(int i=0; itype; i++) { + cellwalker cw2 = cw + i + wstep; + if(!aq.count(cw2.at)) { + enlist(cw2.at); + if(aqs.count(cw2.at)) unifications.emplace_back(cw0, aqs[cw2.at]); + } + else { + auto cw3 = ufind(cw2); + auto cw4 = cw3 + wstep - i; + if(aq.count(cw4.at)) unifications.emplace_back(cw4, cw); + } + } + + if(next_id > 10000) throw aq_overflow(); + } + + /* vector masters; + for(auto& data: aq) if(data.first == data.second.parent.at) masters.push_back(data.first); + + println(hlog, "masters size = ", isize(masters)); */ + + } + +int aq_max = 100; +bool running = false; +bool displaying = false; + +set seen_hashes; + +void recurse() { + if(!running) return; + if(displaying) { + int tick = SDL_GetTicks(); + if(tick > history::lastprogress + history::progress_each) { + history::lastprogress = tick; + mainloopiter(); + } + } + vector active; + int numopen = 0; + vector backup; + buckethash_t hash = 0; + for(auto p: allaq) { + auto ref = ufind(p->where); + hashmix(hash, aq.at(ref.at).id); + hashmix(hash, ref.spin); + backup.push_back(p->parent); + if(p->parent.at == p->where) { + active.push_back(p->where); + if(!p->closed) numopen++; + } + } + if(seen_hashes.count(hash)) return; + seen_hashes.insert(hash); + + cellwalker cw0 = currentmap->gamestart(); + + println(hlog, "active ", isize(active), " numopen = ", numopen, " actives = ", active); + if(numopen == 0) { + println(hlog, "closed found!"); + int shid = shvid(cw0.at); + vector bqo = {999}; + int cl = arb::current.shapes[shid].cycle_length; + for(int i=0; itype; i+=cl) { + auto qo = quotient_output(i); + if(qo < bqo) bqo = qo; + } + + buckethash_t vhash = 0; + for(auto i: bqo) hashmix(vhash, i); + if(seen_outputs.count(vhash)) return; + seen_outputs.insert(vhash); + println(hlog, "[", isize(all_found), "] ", bqo); + all_found.push_back(qdata{format("%016lX", (long) vhash), bqo}); + if(!(cgflags & qCLOSED)) return; + } + indenter ind(2); + + int shid = shvid(cw0.at); + int cl = arb::current.shapes[shid].cycle_length; + + for(auto li: active) if(li != cw0.at) { + if(shvid(li) != shid) continue; + for(int ro=0; rotype; ro+=cl) { + unifications.emplace_back(cw0, cellwalker(li, ro)); + bool b = apply_uni(); + if(b) recurse(); + else unifications.clear(); + } + auto backup_iterator = backup.begin(); + for(auto p: allaq) p->parent = *(backup_iterator++); + } + } + +void auto_create(int num) { + clear_all(); + seen_hashes.clear(); + seen_outputs.clear(); + + cellwalker cw0 = currentmap->gamestart(); + enlist(cw0.at); + + int id = 0; + while(id < num) { + auto aqd = allaq[id++]; + cell *c = aqd->where; + forCellCM(c1, c) if(!aq.count(c1)) enlist(c1); + aqd->closed = true; + } + + println(hlog, "calling recurse"); + indenter ind(2); + recurse(); + + println(hlog, "solutions found: ", isize(all_found)); + } + +vector quotient_data; + +void enable_by_id(int id) { + if(id >= isize(all_found)) throw hr_exception("not that many AQ quotients known"); + stop_game(); + cgflags |= qANYQ | qCLOSED; + quotient_data = all_found[id].connections; + start_game(); + } + +struct hrmap_autoquotient : hrmap { + + vector> connections; + + pair get_conn(int id, int shape) { + if(id >= isize(connections)) { + int t = arb::current.shapes[shape].size(); + heptagon *h = init_heptagon(t); + h->zebraval = shape; + for(int i=0; ic7 = newCell(t, h); + } + if(id >= isize(connections)) throw hr_exception("illegal values in autoquotient"); + return connections[id]; + } + + hrmap_autoquotient() { + get_conn(0, 0); + for(int i=0; izebraval].connections[co.second].sid); + println(hlog, "connectng ", co, " to ", co2); + co.first->c.connect(co.second, co2.first, co2.second, 0); + } + println(hlog, "connections created"); + } + + heptagon *getOrigin() override { + return connections[0].first; + } + + transmatrix adj(heptagon *h, int dir) override { + return arb::get_adj(arb::current_or_slided(), h->zebraval, dir, h->move(dir)->zebraval, h->c.spin(dir), false); + } + + int shvid(cell *c) override { + return c->master->zebraval; + } + + hyperpoint get_corner(cell *c, int cid, ld cf) override { + auto& sh = arb::current_or_slided().shapes[c->master->zebraval]; + int id = gmod(cid, c->type); + if(sh.angles[gmod(id-1, c->type)] <= 0) return sh.vertices[id]; + return normalize(C0 + (sh.vertices[id] - C0) * 3 / cf); + } + }; + +void show_auto_dialog() { + cmode = sm::SIDE | sm::DIALOG_STRICT_X; + gamescreen(); + dialog::init(XLAT("auto-generate quotients")); + add_edit(aq_max); + dialog::addBoolItem(XLAT("running"), running, 'r'); + dialog::add_action([] { + println(hlog, "action"); + if(!running) { + running = true; + displaying = true; + all_found.clear(); + auto_create(aq_max); + } + running = false; + }); + dialog::addSelItem(XLAT("quotients found"), its(isize(all_found)), 'n'); + if(running) dialog::addBreak(100); + else dialog::addBack(); + dialog::display(); + // todo actually prevent backing + } + +EX void show_dialog() { + cmode = sm::SIDE | sm::DIALOG_STRICT_X; + gamescreen(); + dialog::init(XLAT("arbitrary quotients")); + + dialog::start_list(2000, 2000, 'A'); + + for(auto& q: all_found) { + dialog::addBoolItem(q.name, q.connections == quotient_data, dialog::list_fake_key++); + dialog::add_action([q] { + stop_game(); + cgflags |= qANYQ | qCLOSED; + quotient_data = q.connections; + ginf[geometry].quotient_name = q.name; + start_game(); + }); + } + dialog::end_list(); + + dialog::addItem("auto-generate", 'g'); + dialog::add_action_push(show_auto_dialog); + if(cgflags & qANYQ) { + dialog::addItem("disable", 'd'); + dialog::add_action([] { + stop_game(); + cgflags &=~ qANYQ; + if(!sphere) cgflags &=~ qCLOSED; + quotient_data = {}; + start_game(); + }); + } + else dialog::addBreak(100); + dialog::addBack(); + dialog::display(); + } + +auto aqhook = + arg::add3("-aq-test", [] { try { default_list(); create(); } catch(aq_overflow&) { println(hlog, "AQ overflow"); } }) ++ arg::add3("-aq-auto", [] { displaying = false; running = true; arg::shift(); auto_create(arg::argi()); }) ++ arg::add3("-aq-enable", [] { arg::shift(); enable_by_id(arg::argi()); }) ++ arg::add3("-d:aq", [] { arg::launch_dialog(show_dialog); }) ++ addHook(hooks_configfile, 100, [] { + param_i(aq_max, "aq_max") + -> editable(1, 500, 10, "limit on the quotient size", "", 'm'); + }) ++ addHook(hooks_newmap, 0, [] { + if(geometry == gArbitrary && quotient) { + return (hrmap*) new hrmap_autoquotient; + } + return (hrmap*) nullptr; + }); + + +} + +} \ No newline at end of file diff --git a/geom-exp.cpp b/geom-exp.cpp index 43759e9b..6d4512b1 100644 --- a/geom-exp.cpp +++ b/geom-exp.cpp @@ -633,6 +633,8 @@ EX void select_quotient() { pushScreen(product::show_config); else if(mtwisted) hybrid::configure_period(); + else if(arb::in()) + pushScreen(arbiquotient::show_dialog); else { vector choices; for(int i=0; iallcells()); + gd.euler = UNKNOWN; + break; + default: gd.worldsize = isize(currentmap->allcells()); println(hlog, "warning: Euler characteristics unknown, worldsize = ", gd.worldsize); @@ -851,6 +858,8 @@ EX geometry_data compute_geometry_data() { if(gd.euler < 0 && !closed_manifold) gd.worldsize = -gd.worldsize; + if(arb::in()) gd.worldsize = isize(currentmap->allcells()); + auto& spf = gd.spf; spf = its(ts); if(0) ; @@ -952,6 +961,7 @@ EX geometry_data compute_geometry_data() { if(WDIM == 3) gd.euler = 0; gd.demigenus = 2 - gd.euler; + if(gd.euler == UNKNOWN) gd.demigenus = UNKNOWN; return gd; } @@ -1179,12 +1189,12 @@ EX void showEuclideanMenu() { add_size_action(); if(closed_manifold) { - dialog::addSelItem(XLAT("Euler characteristics"), its(gd.euler), 0); + dialog::addSelItem(XLAT("Euler characteristics"), gd.euler == UNKNOWN ? "?" : its(gd.euler), 0); if(WDIM == 3) ; else if(nonorientable) - dialog::addSelItem(XLAT("demigenus"), its(gd.demigenus), 0); + dialog::addSelItem(XLAT("demigenus"), gd.demigenus == UNKNOWN ? "?" : its(gd.demigenus), 0); else - dialog::addSelItem(XLAT("genus"), its(gd.demigenus/2), 0); + dialog::addSelItem(XLAT("genus"), gd.demigenus == UNKNOWN ? "?" : its(gd.demigenus/2), 0); } else dialog::addBreak(200); diff --git a/history.cpp b/history.cpp index 1230c589..d046f7b5 100644 --- a/history.cpp +++ b/history.cpp @@ -194,7 +194,7 @@ EX namespace history { void handleKeyC(int sym, int uni); - int lastprogress; + EX int lastprogress; EX void progress_screen() { gamescreen(); diff --git a/hyper.cpp b/hyper.cpp index 0b8e2a57..a9c080da 100644 --- a/hyper.cpp +++ b/hyper.cpp @@ -134,6 +134,7 @@ #include "intra.cpp" #include "crossbow.cpp" #include "fundamental.cpp" +#include "arbiquotient.cpp" #if CAP_ROGUEVIZ #include "rogueviz/rogueviz-all.cpp"