#pragma once #include #include #include #include #include #include #include #include "combo_iterator.hpp" /** * Produce a list of all generators for the group context. The range [0..group.ngens). */ std::vector generators(const tc::Group &context) { std::vector g_gens(context.ngens); std::iota(g_gens.begin(), g_gens.end(), 0); return g_gens; } namespace { /** * Determine which of g_gens are the correct names for sg_gens within the current context */ std::vector recontext_gens( const tc::Group &context, std::vector g_gens, std::vector sg_gens) { std::sort(g_gens.begin(), g_gens.end()); int inv_gen_map[context.ngens]; for (size_t i = 0; i < g_gens.size(); i++) { inv_gen_map[g_gens[i]] = i; } std::vector s_sg_gens; s_sg_gens.reserve(sg_gens.size()); for (const auto gen : sg_gens) { s_sg_gens.push_back(inv_gen_map[gen]); } std::sort(s_sg_gens.begin(), s_sg_gens.end()); return s_sg_gens; } /** * Solve the cosets generated by sg_gens within the subgroup generated by g_gens of the group context */ tc::Cosets solve( const tc::Group &context, const std::vector &g_gens, const std::vector &sg_gens ) { const auto proper_sg_gens = recontext_gens(context, g_gens, sg_gens); return context.subgroup(g_gens).solve(proper_sg_gens); } /** * Apply some context transformation to all primitives of this mesh. */ template void apply(const tc::Cosets &table, int gen, Prims &mat) { auto data = mat.data(); for (int i = 0; i < mat.size(); ++i) { data[i] = table.get(data[i], gen); } } } template class Mesh { public: const tc::Group *g; // todo this needs to be handled more consistently std::vector ctx; Prims prims; Mesh(const tc::Group &g_, std::vector ctx_, size_t cols); static Mesh fill(const tc::Group &g, std::vector ctx); template static Mesh hull(const tc::Group &g, std::vector ctx, const SC &sub_ctxs); Mesh recontext(std::vector ctx_); std::vector> tile(const std::vector &ctx_); Mesh fan(unsigned root); [[nodiscard]] size_t size() const { return prims.size(); } [[nodiscard]] size_t rows() const { return prims.rows(); } [[nodiscard]] size_t cols() const { return prims.cols(); } [[nodiscard]] unsigned *data() { return prims.data(); } [[nodiscard]] const unsigned *data() const { return prims.data(); } }; template M merge(const std::vector &meshes) { if (meshes.empty()) throw std::logic_error("cannot merge an empty list of meshes"); auto g = meshes[0].g; auto ctx = meshes[0].ctx; size_t cols = 0; for (const auto &mesh : meshes) { cols += mesh.prims.cols(); } M res(*g, ctx, cols); size_t offset = 0; for (const auto &mesh : meshes) { res.prims.middleCols(offset, mesh.prims.cols()) = mesh.prims; offset += mesh.prims.cols(); } return res; } template Mesh Mesh::recontext(std::vector ctx_) { Mesh res = *this; res.ctx = ctx_; const auto proper_sg_gens = recontext_gens(*g, res.ctx, ctx); const auto table = solve(*g, res.ctx, {}); const auto path = solve(*g, ctx, {}).path; auto map = path.template walk(0, proper_sg_gens, [table](int coset, int gen) { return table.get(coset, gen); }); auto data = res.prims.data(); for (int i = 0; i < res.prims.size(); ++i) { data[i] = map[data[i]]; } return res; } template std::vector> Mesh::tile(const std::vector &ctx_) { auto base = recontext(ctx_); auto table = solve(*g, base.ctx, {}); auto path = solve(*g, base.ctx, ctx).path; std::vector> res = path.template walk, int>( base, generators(*g), [&](Mesh mesh, int gen) { apply(table, gen, mesh.prims); return mesh; } ); return res; } template Mesh Mesh::fan(unsigned root) { Mesh res(*g, ctx, prims.cols()); res.prims.topRows(1) = Prims<1>::Constant(1, prims.cols(), root); res.prims.bottomRows(N) = prims; return res; } template Mesh::Mesh(const tc::Group &g_, std::vector ctx_, size_t cols) : g(&g_), ctx(std::move(ctx_)) { prims.setZero(N, cols); } template Mesh Mesh::fill(const tc::Group &g, std::vector ctx) { if (ctx.size() + 1 != N) throw std::logic_error("ctx size must be one less than N"); const auto &combos = Combos(ctx, (int)ctx.size() - 1); std::vector> meshes; for (const auto &sub_ctx : combos) { auto base = Mesh::fill(g, sub_ctx); auto parts = base.tile(ctx); parts.erase(parts.begin(), parts.begin() + 1); if (parts.empty()) continue; auto raised = merge(parts); auto fanned = raised.fan(0); meshes.push_back(fanned); } return merge(meshes); } template<> Mesh<1> Mesh<1>::fill(const tc::Group &g, std::vector ctx) { if (not ctx.empty()) throw std::logic_error("ctx must be empty for a trivial Mesh."); return Mesh<1>(g, ctx, 1); } template template Mesh Mesh::hull(const tc::Group &g, const std::vector ctx, const SC &sub_ctxs) { std::vector> parts; for (const auto &sub_ctx : sub_ctxs) { auto face = Mesh::fill(g, sub_ctx).tile(ctx); parts.insert(parts.end(), face.begin(), face.end()); } return merge(parts); }