COMP: Replace toddcox-faster by reimplemented toddcox

This commit is contained in:
David Allemang
2023-01-26 15:00:32 -05:00
parent fd566e200d
commit 6ef6fbf4ac
21 changed files with 1679 additions and 68 deletions

55
tc/src/cosets.cpp Normal file
View File

@@ -0,0 +1,55 @@
#include <tc/core.hpp>
namespace tc {
Cosets<>::Cosets(size_t rank)
: _rank(rank), _order(0), _complete(false), _data() {}
void Cosets<>::set(size_t coset, size_t gen, size_t target) {
set(coset * rank() + gen, target);
}
[[nodiscard]] size_t Cosets<>::get(size_t coset, size_t gen) const {
return get(coset * rank() + gen);
}
[[nodiscard]] bool Cosets<>::isset(size_t coset, size_t gen) const {
return isset(coset * rank() + gen);
}
[[nodiscard]] size_t Cosets<>::rank() const {
return _rank;
}
[[nodiscard]] size_t Cosets<>::order() const {
return _order;
}
[[nodiscard]] bool Cosets<>::complete() const {
return _complete;
}
[[nodiscard]] size_t Cosets<>::size() const {
return _data.size();
}
void Cosets<>::add_row() {
_data.resize(_data.size() + rank(), UNSET);
_order++;
}
void Cosets<>::set(size_t idx, size_t target) {
size_t coset = idx / rank();
size_t gen = idx % rank();
_data[idx] = target;
_data[target * rank() + gen] = coset;
}
[[nodiscard]] size_t Cosets<>::get(size_t idx) const {
return _data[idx];
}
[[nodiscard]] bool Cosets<>::isset(size_t idx) const {
return get(idx) != UNSET;
}
}

42
tc/src/group.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include <tc/core.hpp>
#include <cassert>
namespace tc {
Group<>::Group(size_t rank) : _rank(rank), _mults(_rank * _rank, 2) {
for (int idx = 0; idx < rank; ++idx) {
set(idx, idx, 1);
}
}
void Group<>::set(size_t u, size_t v, Mult m) {
assert(u < rank());
assert(v < rank());
_mults[u * rank() + v] = m;
_mults[v * rank() + u] = m;
}
[[nodiscard]] Mult Group<>::get(size_t u, size_t v) const {
assert(u < rank());
assert(v < rank());
return _mults[u * rank() + v];
}
[[nodiscard]] size_t Group<>::rank() const {
return _rank;
}
[[nodiscard]] Group<> Group<>::sub(std::vector<size_t> const &idxs) const {
Group<> res(idxs.size());
for (int i = 0; i < idxs.size(); ++i) {
for (int j = i; j < idxs.size(); ++j) {
res.set(i, j, get(idxs[i], idxs[j]));
}
}
return res;
}
}

25
tc/src/groups.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include <tc/groups.hpp>
#include <fmt/args.h>
#include <fmt/core.h>
#include <numeric>
namespace tc {
Group<> schlafli(const std::vector<unsigned int> &mults) {
Group<> res(mults.size() + 1);
for (size_t i = 0; i < mults.size(); ++i) {
res.set(i, i + 1, mults[i]);
}
return res;
}
Group<> vcoxeter(const std::string &symbol, const std::vector<unsigned int> &values) {
fmt::dynamic_format_arg_store<fmt::format_context> ds;
for (const auto &value: values) {
ds.push_back(value);
}
return coxeter(fmt::vformat(symbol, ds));
}
}

299
tc/src/lang.cpp Normal file
View File

@@ -0,0 +1,299 @@
#include <vector>
#include <stack>
#include <string>
#include <cassert>
#include <tc/core.hpp>
#include <tc/groups.hpp>
#include <peglib.h>
#include <fmt/core.h>
#include <fmt/ranges.h>
#include <numeric>
struct Graph {
size_t rank{};
std::vector<tc::Group<>::Rel> edges{};
};
struct Op {
enum Code {
LINK,
PUSH,
POP,
LOOP,
FREE,
};
Code code: 4;
unsigned int value: 12;
explicit Op(Code code, unsigned int value = 0)
: code(code), value(value) {}
};
template<>
struct fmt::formatter<Op> {
template<typename ParseContext>
constexpr auto parse(ParseContext &ctx) {
return ctx.begin();
}
template<typename FormatContext>
auto format(const Op &op, FormatContext &ctx) {
switch (op.code) {
case Op::LINK:
return fmt::format_to(ctx.out(), "link({})",
(unsigned int) op.value);
case Op::PUSH:
return fmt::format_to(ctx.out(), "push()");
case Op::POP:
return fmt::format_to(ctx.out(), "pop()");
case Op::LOOP:
return fmt::format_to(ctx.out(), "loop()");
case Op::FREE:
return fmt::format_to(ctx.out(), "free()");
default:
return fmt::format_to(ctx.out(), "[{}]({})",
(unsigned int) op.code,
(unsigned int) op.value);
}
}
};
struct Factor {
unsigned int mode;
std::vector<unsigned int> orders;
};
struct codegen {
std::vector<Op> ops;
void link(unsigned int order) {
ops.emplace_back(Op::LINK, order);
}
void push() {
ops.emplace_back(Op::PUSH);
}
void pop() {
ops.emplace_back(Op::POP);
}
void loop() {
ops.emplace_back(Op::LOOP);
}
void free() {
ops.emplace_back(Op::FREE);
}
template<typename It>
void insert(It begin, It end) {
std::copy(begin, end, std::back_inserter(ops));
}
void insert(const codegen &o) {
insert(o.ops.begin(), o.ops.end());
}
};
static const std::string GRAMMAR = R"(
root <- term+
term <- product / op
op <- block / link
block <- '(' root ')' / '{' root '}' / '[' root ']'
link <- int / '-'
product <- op '*' factor
factor <- int / '{' int+ '}' / '[' int+ ']'
int <- < [0-9]+ >
%whitespace <- [ \t\n\r]*
)";
peg::parser build_parser() {
peg::parser parser;
parser.set_logger([](size_t line, size_t col, const std::string &msg, const std::string &rule) {
fmt::print(stderr, "{}:{} [{}] {}\n", line, col, rule, msg);
});
auto ok = parser.load_grammar(GRAMMAR);
assert(ok);
parser["int"] = [](const peg::SemanticValues &vs) -> std::any {
return vs.token_to_number<unsigned int>();
};
parser["link"] = [](const peg::SemanticValues &vs) -> std::any {
codegen cg;
if (vs.choice() == 0) {
auto order = std::any_cast<unsigned int>(vs[0]);
cg.link(order);
} else {
cg.free();
}
return cg;
};
parser["root"] = [](const peg::SemanticValues &vs) -> std::any {
codegen cg;
for (const auto &sub: vs) {
cg.insert(std::any_cast<codegen>(sub));
}
return cg;
};
parser["block"] = [](const peg::SemanticValues &vs) -> std::any {
if (vs.choice() == 0) return vs[0];
codegen cg;
cg.push();
cg.insert(std::any_cast<codegen>(vs[0]));
if (vs.choice() == 1) cg.loop();
cg.pop();
return cg;
};
parser["factor"] = [](const peg::SemanticValues &vs) -> std::any {
return Factor{
(unsigned int) vs.choice(),
vs.transform<unsigned int>(),
};
};
parser["product"] = [](const peg::SemanticValues &vs) -> std::any {
auto sub = std::any_cast<codegen>(vs[0]);
auto fac = std::any_cast<Factor>(vs[1]);
codegen cg;
for (const auto &order: fac.orders) {
if (fac.mode == 0) {
for (unsigned int i = 0; i < order; ++i) {
cg.insert(sub);
}
} else {
cg.push();
for (unsigned int i = 0; i < order; ++i) {
cg.insert(sub);
}
if (fac.mode == 1) cg.loop();
cg.pop();
}
}
return cg;
};
return parser;
}
#ifndef NDEBUG
peg::parser build_ast_parser() {
peg::parser parser;
parser.set_logger([](size_t line, size_t col, const std::string &msg, const std::string &rule) {
fmt::print(stderr, "{}:{} [{}] {}\n", line, col, rule, msg);
});
auto ok = parser.load_grammar(GRAMMAR);
assert(ok);
parser.enable_ast();
return parser;
}
#endif
std::vector<Op> compile(const std::string &source) {
#ifndef NDEBUG
static peg::parser ast_parser = build_ast_parser();
std::shared_ptr<peg::Ast> ast;
bool ast_ok = ast_parser.parse(source, ast);
assert(ast_ok);
// std::cout << peg::ast_to_s(ast) << std::endl;
#endif
static peg::parser parser = build_parser();
codegen cg;
bool ok = parser.parse(source, cg);
assert(ok);
return cg.ops;
}
Graph eval(const std::vector<Op> &ops) {
std::vector<std::stack<size_t>> stacks(1);
Graph g;
stacks.back().emplace(g.rank++);
for (const auto &op: ops) {
switch (op.code) {
case Op::FREE:
case Op::LINK: {
tc::Mult order = tc::FREE;
if (op.code == Op::LINK) {
order = op.value;
}
auto top = stacks.back().top();
auto curr = g.rank++;
stacks.back().emplace(curr);
g.edges.emplace_back(top, curr, order);
break;
}
case Op::PUSH: {
stacks.emplace_back();
auto ptop = stacks[stacks.size() - 2].top();
stacks.back().emplace(ptop);
break;
}
case Op::POP: {
stacks.pop_back();
break;
}
case Op::LOOP: {
g.rank--;
auto ptop = stacks[stacks.size() - 2].top();
auto &[top, _, order] = g.edges.back();
stacks.back().pop();
g.edges.pop_back();
g.edges.emplace_back(top, ptop, order);
break;
}
default:
throw std::runtime_error("Invalid opcode");
}
}
return g;
}
namespace tc {
Group<> coxeter(const std::string &symbol) {
auto ops = compile(symbol);
auto diagram = eval(ops);
Group<> res(diagram.rank);
for (const auto &[i, j, m]: diagram.edges) {
res.set(i, j, m);
}
return res;
}
}

211
tc/src/solve.cpp Normal file
View File

@@ -0,0 +1,211 @@
#include <algorithm>
#include <queue>
#include <utility>
#include <vector>
#include <tc/core.hpp>
namespace tc {
/**
* Each coset is associated a row in each table.
* Rows document the "loops" formed by
*/
struct Row {
bool free: 1;
bool idem: 1;
unsigned int gnr: 14; // progress through the loop
unsigned int lst_idx: 32; // the coset that would complete the loop
Row() : free(true), idem(false), gnr(0), lst_idx(0) {}
};
struct Tables {
std::vector<Group<>::Rel> rels;
std::vector<std::vector<Row>> rows;
explicit Tables(std::vector<Group<>::Rel> rels)
: rels(std::move(rels)), rows() {
}
[[nodiscard]] size_t size() const {
return rels.size();
}
void add_row() {
rows.emplace_back(rels.size());
}
};
[[nodiscard]] Cosets<> Group<>::solve(std::vector<size_t> const &idxs, size_t bound) const {
// region Initialize Cosets Table
Cosets<> cosets(rank());
cosets.add_row();
if (rank() == 0) {
cosets._complete = true;
return cosets;
}
for (size_t g: idxs) {
if (g < rank())
cosets.set(0, g, 0);
}
// endregion
// region Initialize Relation Tables
std::vector<Group<>::Rel> rels;
for (int i = 0; i < rank(); ++i) {
for (int j = i + 1; j < rank(); ++j) {
// The algorithm only works for Coxeter groups; multiplicities m_ii=1 are assumed. Relation tables
// _may_ be added for them, but they are redundant and hurt performance so are skipped.
if (i == j) continue;
// Coxeter groups admit infinite multiplicities, represented by contexpr tc::FREE. Relation tables
// for these should be skipped.
auto m = get(i, j);
if (m == FREE) {
continue;
}
rels.emplace_back(i, j, m);
}
}
Tables rel_tables(rels);
std::vector<std::vector<size_t>> tables_for(rank());
int rel_idx = 0;
for (const auto &[i, j, m]: rels) {
tables_for[i].push_back(rel_idx);
tables_for[j].push_back(rel_idx);
rel_idx++;
}
std::vector<size_t> lst_vals;
rel_tables.add_row();
for (int table_idx = 0; table_idx < rel_tables.size(); ++table_idx) {
const auto &[i, j, m] = rel_tables.rels[table_idx];
Row &row = rel_tables.rows[0][table_idx];
if (!cosets.isset(0, i) && !cosets.isset(0, j)) {
row.lst_idx = lst_vals.size();
lst_vals.push_back(0);
row.free = false;
row.gnr = 0;
} else {
row.free = false;
row.gnr = 1;
row.idem = true;
}
}
// endregion
size_t idx = 0;
size_t fact_idx;
size_t coset, gen, target, lst;
while (true) {
// find next unknown product
while (idx < cosets.size() and cosets.isset(idx))
idx++;
if (cosets.order() >= bound) {
return cosets;
}
// if there are none, then return
if (idx == cosets.size()) {
// todo unrolled linked list interval
// rel_tables.del_rows_to(idx / ngens);
break;
}
// the unknown product must be a new coset, so add it
target = cosets.order();
cosets.add_row();
rel_tables.add_row();
// queue of products that equal target
std::queue<size_t> facts;
facts.push(idx); // new product should be recorded and propagated
// todo unrolled linked list interval
// rel_tables.del_rows_to(coset);
// find all products which also lead to target
while (!facts.empty()) {
fact_idx = facts.front();
facts.pop();
// skip if this product was already learned
if (cosets.get(fact_idx) != -1) continue;
cosets.set(fact_idx, target);
coset = fact_idx / rank();
gen = fact_idx % rank();
// If the product stays within the coset todo
for (size_t table_idx: tables_for[gen]) {
auto &[i, j, m] = rel_tables.rels[table_idx];
auto &trow = rel_tables.rows[target][table_idx];
auto &crow = rel_tables.rows[coset][table_idx];
size_t other_gen = (i == gen) ? j : i;
// Test if loop is closed
if (trow.free) {
trow = crow;
trow.gnr++;
if (target == coset) {
trow.idem = true;
}
if (trow.idem) {
if (trow.gnr == m) {
// loop is closed, but idempotent, so the target links to itself via the other generator.
// todo might be able to move this logic up into the (target == coset) block and avoid those computations.
facts.push(target * rank() + other_gen);
}
} else {
if (trow.gnr == m - 1) {
// loop is almost closed. record that the target closes this loop.
lst_vals[trow.lst_idx] = target;
} else if (trow.gnr == m) {
// loop is closed. We know the last element in the loop must link with this one.
lst = lst_vals[trow.lst_idx];
// delete trow.lst_ptr;
facts.push(lst * rank() + other_gen);
}
}
}
}
}
// If any target row wasn't identified with a loop,
// then assign it a new loop.
for (size_t table_idx = 0; table_idx < rel_tables.size(); table_idx++) {
auto &[i, j, m] = rel_tables.rels[table_idx];
auto &trow = rel_tables.rows[target][table_idx];
if (trow.free) {
if ((cosets.get(target, i) != target) and
(cosets.get(target, j) != target)) {
trow.lst_idx = lst_vals.size();
trow.free = false;
lst_vals.push_back(0);
trow.gnr = 0;
} else {
trow.free = false;
trow.gnr = 1;
trow.idem = true;
}
}
}
}
cosets._complete = true;
return cosets;
}
}