Improve tc::Group, tc::Cosets API.

This is a combination of many commits:

---------------------------------------------------------------------------

final. add public/private for core classes.

wip - big cleanup cosets.

index<>, cosets<>, and group<> all defined

transparent <void> overrides that operate directly on indices, not names

inheritance hierarchy means one can drop or change generator names

still missing path support.

wip - named cosets

fix lang test

wip - named generators

Move Group docstring away from specialization.

Remove tc::Gen; use Group<void>

remove tc::Rel

add thousands separators for benchmark limits

add generators list to tc::group

remove pair_map
This commit is contained in:
David Allemang
2022-11-18 11:46:55 -05:00
parent 928f7d7b45
commit 47de177626
20 changed files with 524 additions and 913 deletions

View File

@@ -18,4 +18,4 @@ include(embed.cmake)
enable_testing()
add_subdirectory(tc)
add_subdirectory(vis)
#add_subdirectory(vis)

View File

@@ -1,13 +1,13 @@
add_library(tc
include/tc/core.hpp
include/tc/cosets.hpp
include/tc/group.hpp
include/tc/groups.hpp
include/tc/pair_map.hpp
include/tc/util.hpp
src/core.cpp
src/cosets.cpp
src/group.cpp
src/groups.cpp
src/lang.cpp
src/solve.cpp
)
target_link_libraries(tc peglib::peglib fmt::fmt)
target_include_directories(tc PUBLIC include)

View File

@@ -1,2 +1,5 @@
add_executable(benchmark benchmark.cpp)
target_link_libraries(benchmark PUBLIC tc fmt::fmt)
add_executable(named named.cpp)
target_link_libraries(named PUBLIC tc fmt::fmt)

View File

@@ -11,20 +11,20 @@
void bench(
const std::string &group_expr,
const std::string &symbol,
const std::vector<tc::Gen> &gens,
const tc::Coset bound = tc::UNBOUNDED
const std::vector<size_t> &gens,
const size_t bound = SIZE_MAX
) {
tc::Group group = tc::coxeter(symbol);
tc::Group<> group = tc::coxeter(symbol);
std::clock_t s = std::clock();
tc::Cosets cosets = tc::solve(group, gens, bound);
tc::Cosets<> cosets = group.solve(gens, bound);
std::clock_t e = std::clock();
auto time = (double) (e - s) / CLOCKS_PER_SEC;
tc::Coset order = cosets.size();
size_t order = cosets.order();
auto cos_s = (size_t) (order / time);
bool complete = cosets.complete;
bool complete = cosets.complete();
std::string name = fmt::format("{}/{}", group_expr, gens);
std::string row = fmt::format(
@@ -80,59 +80,59 @@ int main(int argc, char *argv[]) {
// Affine Groups
// ~A_n: {3 * `n+1`}
bench("~A_5", "{3 * 6}", {}, 1000000);
bench("~A_6", "{3 * 7}", {}, 1000000);
bench("~A_7", "{3 * 8}", {}, 1000000);
bench("~A_8", "{3 * 9}", {}, 1000000);
bench("~A_5", "{3 * 6}", {}, 10'000'000);
bench("~A_6", "{3 * 7}", {}, 10'000'000);
bench("~A_7", "{3 * 8}", {}, 10'000'000);
bench("~A_8", "{3 * 9}", {}, 10'000'000);
// ~B_n: 4 3 * `n-3` 3 * [1 1]
bench("~B_5", "4 3 * 2 3 * [1 1]", {}, 1000000);
bench("~B_6", "4 3 * 3 3 * [1 1]", {}, 1000000);
bench("~B_7", "4 3 * 4 3 * [1 1]", {}, 1000000);
bench("~B_8", "4 3 * 5 3 * [1 1]", {}, 1000000);
bench("~B_5", "4 3 * 2 3 * [1 1]", {}, 10'000'000);
bench("~B_6", "4 3 * 3 3 * [1 1]", {}, 10'000'000);
bench("~B_7", "4 3 * 4 3 * [1 1]", {}, 10'000'000);
bench("~B_8", "4 3 * 5 3 * [1 1]", {}, 10'000'000);
// ~B_n: 4 3 * `n-2` 4
bench("~C_5", "4 3 * 3 4", {}, 1000000);
bench("~C_6", "4 3 * 4 4", {}, 1000000);
bench("~C_7", "4 3 * 5 4", {}, 1000000);
bench("~C_8", "4 3 * 6 4", {}, 1000000);
bench("~C_5", "4 3 * 3 4", {}, 10'000'000);
bench("~C_6", "4 3 * 4 4", {}, 10'000'000);
bench("~C_7", "4 3 * 5 4", {}, 10'000'000);
bench("~C_8", "4 3 * 6 4", {}, 10'000'000);
// ~D_n: 3 * [1 1] 3 * `n-4` 3 * [1 1]
bench("~D_5", "3 * [1 1] 3 * 1 3 * [1 1]", {}, 1000000);
bench("~D_6", "3 * [1 1] 3 * 2 3 * [1 1]", {}, 1000000);
bench("~D_7", "3 * [1 1] 3 * 3 3 * [1 1]", {}, 1000000);
bench("~D_8", "3 * [1 1] 3 * 4 3 * [1 1]", {}, 1000000);
bench("~D_5", "3 * [1 1] 3 * 1 3 * [1 1]", {}, 10'000'000);
bench("~D_6", "3 * [1 1] 3 * 2 3 * [1 1]", {}, 10'000'000);
bench("~D_7", "3 * [1 1] 3 * 3 3 * [1 1]", {}, 10'000'000);
bench("~D_8", "3 * [1 1] 3 * 4 3 * [1 1]", {}, 10'000'000);
// grid: `p` `q` ; 2(p+q) = pq
// triangle: `p` `q` `r` ; 1/p + 1/q + 1/r = 1
// Special Affine Groups
bench("~I_1", "-", {}, 1000000);
bench("~E_6", "3 * [2 2 2]", {}, 1000000);
bench("~E_7", "3 * [1 3 3]", {}, 1000000);
bench("~E_8", "3 * [1 2 5]", {}, 1000000);
// bench("E_9", "3 * [1 2 5]", {}, 1000000); // ~E_8 == E_9
bench("~F_4", "3 4 3 3", {}, 1000000);
bench("~G_2", "6 3", {}, 1000000);
bench("~I_1", "-", {}, 10'000'000);
bench("~E_6", "3 * [2 2 2]", {}, 10'000'000);
bench("~E_7", "3 * [1 3 3]", {}, 10'000'000);
bench("~E_8", "3 * [1 2 5]", {}, 10'000'000);
// bench("E_9", "3 * [1 2 5]", {}, 10'000'000); // ~E_8 == E_9
bench("~F_4", "3 4 3 3", {}, 10'000'000);
bench("~G_2", "6 3", {}, 10'000'000);
// Hyperbolic Groups
// grid: `p` `q` ; 2(p+q) < pq
// triangle: `p` `q` `r` ; 1/p + 1/q + 1/r < 1
// Special Hyperbolic Groups
bench("-BH_3", "4 3 5", {}, 1000000);
bench("-K_3", "5 3 5", {}, 1000000);
bench("-J_3", "3 5 3", {}, 1000000);
// bench("~H_3", "3 5 3", {}, 1000000); // -J_3 == ~H_3
bench("-DH_3", "5 3 * [1 1]", {}, 1000000);
bench("^AB_3", "{3 3 3 4}", {}, 1000000);
bench("^AH_3", "{3 3 3 5}", {}, 1000000);
bench("^BB_3", "{3 4 3 4}", {}, 1000000);
bench("^BH_3", "{3 4 3 5}", {}, 1000000);
bench("^HH_3", "{3 5 3 5}", {}, 1000000);
bench("-H_4", "5 3 3 3", {}, 1000000);
// bench("~H_4", "5 3 3 3", {}, 1000000); // -H_4 == ~H_4 == H_5
// bench("H_5", "5 3 3 3", {}, 1000000);
bench("-BH_4", "4 3 3 5", {}, 1000000);
bench("-K_4", "5 3 3 5", {}, 1000000);
bench("-DH_4", "5 3 3 * [1 1]", {}, 1000000);
bench("^AF_4", "{3 3 3 3 4}", {}, 1000000);
bench("-BH_3", "4 3 5", {}, 10'000'000);
bench("-K_3", "5 3 5", {}, 10'000'000);
bench("-J_3", "3 5 3", {}, 10'000'000);
// bench("~H_3", "3 5 3", {}, 10'000'000); // -J_3 == ~H_3
bench("-DH_3", "5 3 * [1 1]", {}, 10'000'000);
bench("^AB_3", "{3 3 3 4}", {}, 10'000'000);
bench("^AH_3", "{3 3 3 5}", {}, 10'000'000);
bench("^BB_3", "{3 4 3 4}", {}, 10'000'000);
bench("^BH_3", "{3 4 3 5}", {}, 10'000'000);
bench("^HH_3", "{3 5 3 5}", {}, 10'000'000);
bench("-H_4", "5 3 3 3", {}, 10'000'000);
// bench("~H_4", "5 3 3 3", {}, 10'000'000); // -H_4 == ~H_4 == H_5
// bench("H_5", "5 3 3 3", {}, 10'000'000);
bench("-BH_4", "4 3 3 5", {}, 10'000'000);
bench("-K_4", "5 3 3 5", {}, 10'000'000);
bench("-DH_4", "5 3 3 * [1 1]", {}, 10'000'000);
bench("^AF_4", "{3 3 3 3 4}", {}, 10'000'000);
return EXIT_SUCCESS;
}

55
tc/bench/named.cpp Normal file
View File

@@ -0,0 +1,55 @@
#include <string>
#include <vector>
#include <fmt/core.h>
#include <fmt/ranges.h>
#include <tc/core.hpp>
template<typename G>
void show(G const &g) { // todo add formatter for groups, cosets.
fmt::print(" | ");
for (const auto &v: g.gens()) {
fmt::print("{} ", v);
}
fmt::print("\n");
for (const auto &u: g.gens()) {
fmt::print("{} | ", u);
for (const auto &v: g.gens()) {
fmt::print("{} ", g.get(u, v));
}
fmt::print("\n");
}
}
int main() {
tc::Group<char> group(4, {'r', 'g', 'b', 'y'});
group.set('r', 'g', 5);
group.set('g', 'b', 4);
group.set('b', 'y', 3);
show(group);
auto sub = group.sub({'r', 'g', 'y'});
show(sub);
auto res = sub.solve({});
fmt::print("res order: {}\n", res.order());
auto cos = sub.solve({'r', 'y'});
fmt::print("order: {}\n", cos.order());
// tc::Group<u_int8_t> group(4, {0, 1, 2, 3});
//
// group.set(0, 1, 5);
// group.set(1, 2, 4);
// group.set(2, 3, 3);
//
// show(group);
//
// auto sub = group.sub({3, 2, 0, 1});
// show(sub);
}

View File

@@ -1,30 +1,220 @@
#pragma once
#include <array>
#include <functional>
#include <vector>
#include <string>
#include <vector> //todo clean up includes. lots of duplicate cstdint, cassert.
#include <cstdint>
#include <cassert>
#include "util.hpp"
#include "cosets.hpp"
#include "group.hpp"
#include <limits>
namespace tc {
Cosets solve(const Group &group, const std::vector<Gen> &sub_gens, const Coset &bound = UNBOUNDED);
// todo
///**
// * 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<tc::Gen> &g_gens,
// const std::vector<tc::Gen> &sg_gens
// ) {
// // todo this should also be handled with 'global' generators.
// const auto proper_sg_gens = recontext_gens(context, g_gens, sg_gens);
//
// return tc::solve(context.subgroup(g_gens), proper_sg_gens);
// }
using Mult = u_int16_t;
constexpr Mult FREE = 0;
/**
* @brief Mapping from "global" generator names or objects to indexes used for value lookup.
* @tparam Gen_
*/
template<typename Gen_=void>
struct Index;
/**
* @brief Complete representation of a quotient group. Describes the action of each generator on each coset.
* @tparam Gen_
*/
template<typename Gen_=void>
struct Cosets;
/**
* @brief Manage the presentation of a Coxeter group and enforce constraints
* on the multiplicities of its relations.
* <ul>
* <li>
* <code>m_ij = 1</code> iff <code>i != j</code>
* </li>
* <li>
* <code>m_ij = m_ji</code>
* </li>
* <li>
* If <code>m_ij == inf</code> (<code>tc::FREE</code>) then no relation is imposed.
* </li>
* </ul>
* @see
* <a href="https://en.wikipedia.org/wiki/Coxeter_group#Definition">Coxeter Group (Wikipedia)</a>
*/
template<typename Gen_=void>
struct Group;
template<>
struct Index<> {
size_t operator()(size_t const &idx) const {
return idx;
}
};
template<typename Gen_>
struct Index {
using Gen = Gen_;
std::vector<Gen> _gens{};
explicit Index(std::vector<Gen> gens) : _gens(gens) {}
size_t operator()(Gen const &gen) const {
auto it = std::find(_gens.begin(), _gens.end(), gen);
assert(it != _gens.end());
return it - _gens.begin();
}
};
template<>
struct Cosets<> {
static constexpr size_t UNSET = std::numeric_limits<size_t>::max();
private:
size_t _rank;
size_t _order;
bool _complete;
std::vector<size_t> _data;
public:
Cosets(Cosets const &) = default;
Cosets(Cosets &&) noexcept = default;
~Cosets() = default;
void set(size_t coset, size_t gen, size_t target);
[[nodiscard]] size_t get(size_t coset, size_t gen) const;
[[nodiscard]] bool isset(size_t coset, size_t gen) const;
[[nodiscard]] size_t rank() const;
[[nodiscard]] size_t order() const;
[[nodiscard]] bool complete() const;
[[nodiscard]] size_t size() const;
friend Group<>; // only constructible via Group<>::solve
private:
explicit Cosets(size_t rank);
void add_row();
void set(size_t idx, size_t target);
[[nodiscard]] size_t get(size_t idx) const;
[[nodiscard]] bool isset(size_t idx) const;
};
template<>
struct Group<> {
using Rel = std::tuple<size_t, size_t, Mult>;
private:
size_t _rank;
std::vector<size_t> _mults;
public:
Group(Group const &) = default;
Group(Group &&) noexcept = default;
~Group() = default;
explicit Group(size_t rank);
void set(size_t, size_t, Mult);
[[nodiscard]] Mult get(size_t, size_t) const;
[[nodiscard]] size_t rank() const;
[[nodiscard]] Group sub(std::vector<size_t> const &idxs) const;
[[nodiscard]] Cosets<> solve(std::vector<size_t> const &idxs, size_t bound = SIZE_MAX) const;
};
template<typename Gen_>
struct Cosets : public Cosets<> {
using Gen = Gen_;
private:
Index<Gen> _index;
public:
Cosets(Cosets<> g, std::vector<Gen> gens)
: Cosets<>(g), _index(gens) {}
void set(size_t coset, Gen const &gen, size_t target) {
Cosets<>::set(coset, _index(gen), target);
}
[[nodiscard]] size_t get(size_t coset, Gen const &gen) const {
return Cosets<>::get(coset, _index(gen));
}
[[nodiscard]] bool isset(size_t coset, Gen const &gen) const {
return Cosets<>::isset(coset, _index(gen));
}
[[nodiscard]] std::vector<Gen> gens() const {
return _index._gens;
}
private:
Cosets(size_t rank, std::vector<Gen> gens)
: Cosets<>(rank), _index(gens) {}
};
template<typename Gen_>
struct Group : public Group<> {
using Gen = Gen_;
using Rel = std::tuple<Gen, Gen, Mult>;
private:
Index<Gen> _index;
public:
Group(Group const &) = default;
Group(Group &&) noexcept = default;
Group(Group<> g, std::vector<Gen> gens)
: Group<>(g), _index(gens) {}
Group(size_t rank, std::vector<Gen> gens)
: Group<>(rank), _index(gens) {}
~Group() = default;
void set(Gen const &u, Gen const &v, Mult m) {
Group<>::set(_index(u), _index(v), m);
}
[[nodiscard]] Mult get(Gen const &u, Gen const &v) const {
return Group<>::get(_index(u), _index(v));
}
[[nodiscard]] std::vector<Gen> gens() const {
return _index._gens;
}
[[nodiscard]] Group sub(std::vector<Gen> const &gens) const {
std::vector<size_t> idxs(gens.size());
std::transform(gens.begin(), gens.end(), idxs.begin(), _index);
return Group(Group<>::sub(idxs), gens);
}
[[nodiscard]] Cosets<Gen> solve(std::vector<Gen> const &gens, size_t bound = SIZE_MAX) const {
std::vector<size_t> idxs(gens.size());
std::transform(gens.begin(), gens.end(), idxs.begin(), _index);
return Cosets<Gen>(Group<>::solve(idxs, bound), gens);
}
};
}

View File

@@ -1,67 +0,0 @@
#pragma once
#include <cstdlib>
#include "util.hpp"
namespace tc {
struct Cosets {
Gen ngens;
std::vector<int> data;
Path path;
bool complete = false;
Cosets(const Cosets &) = default;
explicit Cosets(Gen ngens)
: ngens(ngens) {
}
void add_row() {
data.resize(data.size() + ngens, UNSET);
path.add_row();
}
void put(Coset coset, Gen gen, Coset target) {
data[coset * ngens + gen] = target;
data[target * ngens + gen] = coset;
if (path.get(target).from_idx == UNSET) {
path.put(coset, gen, target);
}
}
void put(size_t idx, Coset target) {
Coset coset = idx / ngens;
Gen gen = idx % ngens;
data[idx] = target;
data[target * ngens + gen] = coset;
if (path.get(target).from_idx == UNSET) {
path.put(coset, gen, target);
}
}
[[nodiscard]] Coset get(Coset coset, Gen gen) const {
return data[coset * ngens + gen];
}
[[nodiscard]] Coset get(size_t idx) const {
return data[idx];
}
[[nodiscard]] bool isset(Coset coset, Gen gen) const {
return get(coset, gen) != UNSET;
}
[[nodiscard]] bool isset(size_t idx) const {
return get(idx) != UNSET;
}
[[nodiscard]] tc::Coset size() const {
return path.size();
}
};
}

View File

@@ -1,91 +0,0 @@
#pragma once
#include <cassert>
#include <sstream>
#include <tc/util.hpp>
#include <tc/pair_map.hpp>
namespace tc {
struct Group;
struct SubGroup;
struct Graph {
size_t rank{};
std::vector<tc::Rel> edges{};
};
/**
* @brief Manage the presentation of a Coxeter group and enforce constraints
* on the multiplicities of its relations.
* <ul>
* <li>
* <code>m_ij = 1</code> iff <code>i != j</code>
* </li>
* <li>
* <code>m_ij = m_ji</code>
* </li>
* <li>
* If <code>m_ij == inf</code> (<code>tc::FREE</code>) then no relation is imposed.
* </li>
* </ul>
* @see
* <a href="https://en.wikipedia.org/wiki/Coxeter_group#Definition">Coxeter Group (Wikipedia)</a>
*/
struct Group {
int rank;
tc::pair_map<int> _orders;
Group(const Group &) = default;
explicit Group(int rank, const std::vector<Rel> &rels = {})
: rank(rank), _orders(rank, 2) {
for (int i = 0; i < rank; ++i) {
set(Rel{i, i, 1});
}
for (const auto &rel: rels) {
set(rel);
}
}
explicit Group(const Graph &graph)
: rank(graph.rank), _orders(graph.rank, 2) {
for (const auto &[i, j, order]: graph.edges) {
set({i, j, order});
}
}
void set(const Rel &r) {
auto &[i, j, m] = r;
assert(i != j || m == 1);
_orders(i, j) = m;
}
[[nodiscard]] int get(int i, int j) const {
return _orders(i, j);
}
[[nodiscard]] SubGroup subgroup(const std::vector<tc::Gen> &gens) const;
};
struct SubGroup : public Group {
std::vector<tc::Gen> gen_map;
const Group &parent;
SubGroup(const Group &parent, std::vector<tc::Gen> gen_map)
: Group(gen_map.size()), parent(parent), gen_map() {
std::sort(gen_map.begin(), gen_map.end());
this->gen_map = gen_map;
for (size_t i = 0; i < gen_map.size(); ++i) {
for (size_t j = 0; j < gen_map.size(); ++j) {
int mult = parent.get(gen_map[i], gen_map[j]);
set(Rel(i, j, mult));
}
}
}
};
}

View File

@@ -1,22 +1,23 @@
#pragma once
#include <vector>
#include <string>
#include <tc/group.hpp>
#include <tc/core.hpp>
namespace tc {
/**
* Construct a group from a (simplified) Schlafli Symbol of the form [a, b, ..., c]
* @param mults: The sequence of multiplicites between adjacent generators.
*/
Group schlafli(const std::vector<unsigned int> &mults);
Group<> schlafli(const std::vector<unsigned int> &mults);
Group coxeter(const std::string &symbol);
Group<> coxeter(const std::string &symbol);
Group vcoxeter(const std::string &symbol, const std::vector<unsigned int> &values);
Group<> vcoxeter(const std::string &symbol, const std::vector<unsigned int> &values);
template<typename ...Args>
Group coxeter(const std::string &symbol, const Args &... args) {
Group<> coxeter(const std::string &symbol, const Args &... args) {
std::vector<unsigned int> values = {{args...}};
return vcoxeter(symbol, values);
}

View File

@@ -1,453 +0,0 @@
#pragma once
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <tuple>
#include <vector>
namespace tc {
template<typename T>
struct pair_map {
struct iterator;
struct const_iterator;
struct view;
struct const_view;
private:
size_t _size;
std::vector<T> _data{};
static size_t idx(size_t i, size_t j);
public:
explicit pair_map(size_t size);
explicit pair_map(size_t size, const T &value);
[[nodiscard]] size_t size() const;
T &operator()(size_t i, size_t j);
T operator()(size_t i, size_t j) const;
view of(size_t f);
const_view of(size_t f) const;
const_view cof(size_t f);
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
const_iterator cbegin();
const_iterator cend();
};
template<typename T>
struct pair_map<T>::iterator {
using reference = std::tuple<size_t, size_t, T &>;
private:
pair_map<T> &_map;
size_t _i, _j;
public:
iterator(pair_map<T> &map, size_t i, size_t j);
iterator operator++();
iterator operator++(int) &;
reference operator*();
bool operator!=(const iterator &other);
};
template<typename T>
struct pair_map<T>::const_iterator {
using value_type = std::tuple<size_t, size_t, T>;
private:
const pair_map<T> &_map;
size_t _i, _j;
public:
const_iterator(const pair_map<T> &map, size_t i, size_t j);
const_iterator operator++();
const_iterator operator++(int) &;
value_type operator*();
bool operator!=(const const_iterator &other);
};
template<typename T>
struct pair_map<T>::view {
struct iterator;
struct const_iterator;
private:
pair_map<T> &_map;
size_t _f;
public:
view(pair_map<T> &map, size_t f);
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
const_iterator cbegin();
const_iterator cend();
};
template<typename T>
struct pair_map<T>::view::iterator {
using reference = std::tuple<size_t, size_t, T &>;
private:
pair_map<T> &_map;
size_t _f, _v;
public:
iterator(pair_map<T> &map, size_t f, size_t v);
iterator operator++();
iterator operator++(int);
reference operator*();
bool operator!=(const iterator &other);
};
template<typename T>
struct pair_map<T>::view::const_iterator {
using value_type = std::tuple<size_t, size_t, T>;
private:
const pair_map<T> &_map;
size_t _f, _v;
public:
const_iterator(const pair_map<T> &map, size_t f, size_t v);
const_iterator operator++();
const_iterator operator++(int);
value_type operator*();
bool operator!=(const const_iterator &other);
};
template<typename T>
struct pair_map<T>::const_view {
using const_iterator = typename pair_map<T>::view::const_iterator;
private:
const pair_map<T> &_map;
size_t _f;
public:
const_view(const pair_map<T> &map, size_t f);
const_iterator begin() const;
const_iterator end() const;
const_iterator cbegin();
const_iterator cend();
};
// region pair_map
template<typename T>
size_t pair_map<T>::idx(size_t i, size_t j) {
if (i > j) std::swap(i, j);
return j * (j + 1) / 2 + i;
}
template<typename T>
pair_map<T>::pair_map(size_t size)
: _size(size), _data(size * (size + 1) / 2) {}
template<typename T>
pair_map<T>::pair_map(size_t size, const T &value)
: _size(size), _data(size * (size + 1) / 2, value) {}
template<typename T>
size_t pair_map<T>::size() const {
return _size;
}
template<typename T>
T &pair_map<T>::operator()(size_t i, size_t j) {
return _data[idx(i, j)];
}
template<typename T>
T pair_map<T>::operator()(size_t i, size_t j) const {
return _data[idx(i, j)];
}
template<typename T>
typename pair_map<T>::view pair_map<T>::of(size_t f) {
return view(*this, f);
}
template<typename T>
typename pair_map<T>::const_view pair_map<T>::of(size_t f) const {
return const_view(*this, f);
}
template<typename T>
typename pair_map<T>::const_view pair_map<T>::cof(size_t f) {
return const_view(*this, f);
}
template<typename T>
typename pair_map<T>::iterator pair_map<T>::begin() {
return iterator(*this, 0, 0);
}
template<typename T>
typename pair_map<T>::iterator pair_map<T>::end() {
return iterator(*this, 0, _size);
}
template<typename T>
typename pair_map<T>::const_iterator pair_map<T>::begin() const {
return const_iterator(*this, 0, 0);
}
template<typename T>
typename pair_map<T>::const_iterator pair_map<T>::end() const {
return const_iterator(*this, 0, _size);
}
template<typename T>
typename pair_map<T>::const_iterator pair_map<T>::cbegin() {
return const_iterator(*this, 0, 0);
}
template<typename T>
typename pair_map<T>::const_iterator pair_map<T>::cend() {
return const_iterator(*this, 0, _size);
}
// endregion
// region pair_map::iterator
template<typename T>
pair_map<T>::iterator::iterator(pair_map<T> &map, size_t i, size_t j)
:_map(map), _i(i), _j(j) {}
template<typename T>
typename pair_map<T>::iterator pair_map<T>::iterator::operator++() {
if (++_i > _j) {
_i = 0;
++_j;
}
return *this;
}
template<typename T>
typename pair_map<T>::iterator pair_map<T>::iterator::operator++(int) &{
iterator it = *this;
++this;
return it;
}
template<typename T>
typename pair_map<T>::iterator::reference pair_map<T>::iterator::operator*() {
return std::tie(_i, _j, _map(_i, _j));
}
template<typename T>
bool pair_map<T>::iterator::operator!=(const pair_map::iterator &other) {
return &_map != &other._map || _i != other._i || _j != other._j;
}
// endregion
// region pair_map::const_iterator
template<typename T>
pair_map<T>::const_iterator::const_iterator(const pair_map<T> &map, size_t i, size_t j)
:_map(map), _i(i), _j(j) {}
template<typename T>
typename pair_map<T>::const_iterator pair_map<T>::const_iterator::operator++() {
if (++_i > _j) {
_i = 0;
++_j;
}
return *this;
}
template<typename T>
typename pair_map<T>::const_iterator pair_map<T>::const_iterator::operator++(int) &{
const_iterator it = *this;
++this;
return it;
}
template<typename T>
typename pair_map<T>::const_iterator::value_type pair_map<T>::const_iterator::operator*() {
return std::tuple(_i, _j, _map(_i, _j));
}
template<typename T>
bool pair_map<T>::const_iterator::operator!=(const pair_map::const_iterator &other) {
return &_map != &other._map || _i != other._i || _j != other._j;
}
// endregion
// region pair_map::view
template<typename T>
pair_map<T>::view::view(pair_map<T> &map, size_t f)
: _map(map), _f(f) {}
template<typename T>
typename pair_map<T>::view::iterator pair_map<T>::view::begin() {
return iterator(_map, _f, 0);
}
template<typename T>
typename pair_map<T>::view::iterator pair_map<T>::view::end() {
return iterator(_map, _f, _map._size);
}
template<typename T>
typename pair_map<T>::view::const_iterator pair_map<T>::view::begin() const {
return const_iterator(_map, _f, 0);
}
template<typename T>
typename pair_map<T>::view::const_iterator pair_map<T>::view::end() const {
return const_iterator(_map, _f, _map._size);
}
template<typename T>
typename pair_map<T>::view::const_iterator pair_map<T>::view::cbegin() {
return const_iterator(_map, _f, 0);
}
template<typename T>
typename pair_map<T>::view::const_iterator pair_map<T>::view::cend() {
return const_iterator(_map, _f, _map._size);
}
// endregion
// region pair_map::view::iterator
template<typename T>
pair_map<T>::view::iterator::iterator(pair_map<T> &map, size_t f, size_t v)
: _map(map), _f(f), _v(v) {}
template<typename T>
typename pair_map<T>::view::iterator pair_map<T>::view::iterator::operator++() {
++_v;
return *this;
}
template<typename T>
typename pair_map<T>::view::iterator pair_map<T>::view::iterator::operator++(int) {
iterator it = *this;
++this;
return it;
}
template<typename T>
typename pair_map<T>::view::iterator::reference pair_map<T>::view::iterator::operator*() {
auto [i, j] = std::minmax(_f, _v);
return std::tie(i, j, _map(i, j));
}
template<typename T>
bool pair_map<T>::view::iterator::operator!=(const pair_map::view::iterator &other) {
return &_map != &other._map || _f != other._f || _v != other._v;
}
// endregion
// region pair_map::view::const_iterator
template<typename T>
pair_map<T>::view::const_iterator::const_iterator(const pair_map<T> &map, size_t f, size_t v)
: _map(map), _f(f), _v(v) {}
template<typename T>
typename pair_map<T>::view::const_iterator pair_map<T>::view::const_iterator::operator++() {
++_v;
return *this;
}
template<typename T>
typename pair_map<T>::view::const_iterator pair_map<T>::view::const_iterator::operator++(int) {
const_iterator it = *this;
++this;
return it;
}
template<typename T>
typename pair_map<T>::view::const_iterator::value_type pair_map<T>::view::const_iterator::operator*() {
auto [i, j] = std::minmax(_f, _v);
return std::tuple(i, j, _map(i, j));
}
template<typename T>
bool pair_map<T>::view::const_iterator::operator!=(const pair_map::view::const_iterator &other) {
return &_map != &other._map || _f != other._f || _v != other._v;
}
// endregion
// region pair_map::const_view
template<typename T>
pair_map<T>::const_view::const_view(const pair_map<T> &map, size_t f)
: _map(map), _f(f) {}
template<typename T>
typename pair_map<T>::const_view::const_iterator pair_map<T>::const_view::begin() const {
return const_iterator(_map, _f, 0);
}
template<typename T>
typename pair_map<T>::const_view::const_iterator pair_map<T>::const_view::end() const {
return const_iterator(_map, _f, _map._size);
}
template<typename T>
typename pair_map<T>::const_view::const_iterator pair_map<T>::const_view::cbegin() {
return const_iterator(_map, _f, 0);
}
template<typename T>
typename pair_map<T>::const_view::const_iterator pair_map<T>::const_view::cend() {
return const_iterator(_map, _f, _map._size);
}
// endregion
}

View File

@@ -4,18 +4,11 @@
#include <vector>
#include <algorithm>
#include <functional>
#include <limits>
namespace tc {
using Gen = uint8_t;
using Mult = uint16_t;
constexpr Mult FREE = Mult(-1);
using Coset = uint32_t;
constexpr Coset UNSET = Coset(-1);
constexpr Coset UNBOUNDED = (Coset) (-1);
using Rel = std::tuple<Gen, Gen, Mult>;
// using Mult = uint16_t;
// constexpr Mult FREE = 0;
struct Action {
int from_idx = -1;

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;
}
}

View File

@@ -2,17 +2,18 @@
#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);
Group<> schlafli(const std::vector<unsigned int> &mults) {
Group<> res(mults.size() + 1);
for (size_t i = 0; i < mults.size(); ++i) {
res.set(Rel{i, i + 1, mults[i]});
res.set(i, i + 1, mults[i]);
}
return res;
}
Group vcoxeter(const std::string &symbol, const std::vector<unsigned int> &values) {
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) {

View File

@@ -4,13 +4,19 @@
#include <cassert>
#include <tc/util.hpp>
#include <tc/group.hpp>
#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 {
@@ -191,6 +197,7 @@ peg::parser build_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) {
@@ -203,6 +210,7 @@ peg::parser build_ast_parser() {
return parser;
}
#endif
std::vector<Op> compile(const std::string &source) {
@@ -221,10 +229,10 @@ std::vector<Op> compile(const std::string &source) {
return cg.ops;
}
tc::Graph eval(const std::vector<Op> &ops) {
Graph eval(const std::vector<Op> &ops) {
std::vector<std::stack<size_t>> stacks(1);
tc::Graph g;
Graph g;
stacks.back().emplace(g.rank++);
for (const auto &op: ops) {
@@ -280,9 +288,13 @@ tc::Graph eval(const std::vector<Op> &ops) {
}
namespace tc {
Group coxeter(const std::string &symbol) {
Group<> coxeter(const std::string &symbol) {
auto ops = compile(symbol);
auto diagram = eval(ops);
return Group(diagram);
Group<> res(diagram.rank);
for (const auto &[i, j, m]: diagram.edges) {
res.set(i, j, m);
}
return res;
}
}

View File

@@ -1,15 +1,11 @@
#include <tc/core.hpp>
#include <algorithm>
#include <queue>
#include <utility>
#include <vector>
namespace tc {
SubGroup Group::subgroup(const std::vector<tc::Gen> &gens) const {
return {*this, gens};
}
#include <tc/core.hpp>
namespace tc {
/**
* Each coset is associated a row in each table.
* Rows document the "loops" formed by
@@ -24,10 +20,10 @@ namespace tc {
};
struct Tables {
std::vector<Rel> rels;
std::vector<Group<>::Rel> rels;
std::vector<std::vector<Row>> rows;
explicit Tables(std::vector<Rel> rels)
explicit Tables(std::vector<Group<>::Rel> rels)
: rels(std::move(rels)), rows() {
}
@@ -40,46 +36,44 @@ namespace tc {
}
};
Cosets solve(
const Group &group,
const std::vector<Gen> &sub_gens,
const Coset &bound
) {
auto rank = group.rank;
[[nodiscard]] Cosets<> Group<>::solve(std::vector<size_t> const &idxs, size_t bound) const {
// region Initialize Cosets Table
Cosets cosets(rank);
Cosets<> cosets(rank());
cosets.add_row();
if (rank == 0) {
cosets.complete = true;
if (rank() == 0) {
cosets._complete = true;
return cosets;
}
for (Coset g: sub_gens) {
if (g < rank)
cosets.put(0, g, 0);
for (size_t g: idxs) {
if (g < rank())
cosets.set(0, g, 0);
}
// endregion
// region Initialize Relation Tables
std::vector<std::tuple<Gen, Gen, Mult>> rels;
for (const auto &[i, j, m]: group._orders) {
// 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;
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.
if (m == FREE) {
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);
}
rels.emplace_back(i, j, m);
}
Tables rel_tables(rels);
std::vector<std::vector<size_t>> tables_for(rank);
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);
@@ -87,7 +81,7 @@ namespace tc {
rel_idx++;
}
std::vector<Coset> lst_vals;
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];
@@ -108,26 +102,26 @@ namespace tc {
size_t idx = 0;
size_t fact_idx;
Coset coset, gen, target, lst;
size_t coset, gen, target, lst;
while (true) {
// find next unknown product
while (idx < cosets.data.size() and cosets.isset(idx))
while (idx < cosets.size() and cosets.isset(idx))
idx++;
if (cosets.size() >= bound) {
if (cosets.order() >= bound) {
return cosets;
}
// if there are none, then return
if (idx == cosets.data.size()) {
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.size();
target = cosets.order();
cosets.add_row();
rel_tables.add_row();
@@ -136,8 +130,6 @@ namespace tc {
facts.push(idx); // new product should be recorded and propagated
// todo unrolled linked list interval
// coset = idx / ngens;
// gen = idx % ngens;
// rel_tables.del_rows_to(coset);
// find all products which also lead to target
@@ -148,10 +140,10 @@ namespace tc {
// skip if this product was already learned
if (cosets.get(fact_idx) != -1) continue;
cosets.put(fact_idx, target);
cosets.set(fact_idx, target);
coset = fact_idx / rank;
gen = fact_idx % rank;
coset = fact_idx / rank();
gen = fact_idx % rank();
// If the product stays within the coset todo
for (size_t table_idx: tables_for[gen]) {
@@ -159,7 +151,7 @@ namespace tc {
auto &trow = rel_tables.rows[target][table_idx];
auto &crow = rel_tables.rows[coset][table_idx];
Coset other_gen = (i == gen) ? j : i;
size_t other_gen = (i == gen) ? j : i;
// Test if loop is closed
if (trow.free) {
@@ -174,7 +166,7 @@ namespace tc {
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);
facts.push(target * rank() + other_gen);
}
} else {
if (trow.gnr == m - 1) {
@@ -184,7 +176,7 @@ namespace tc {
// 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);
facts.push(lst * rank() + other_gen);
}
}
}
@@ -213,7 +205,7 @@ namespace tc {
}
}
cosets.complete = true;
cosets._complete = true;
return cosets;
}
}

View File

@@ -1,8 +1,5 @@
include(GoogleTest)
add_executable(test_pair_map test_pair_map.cpp)
target_link_libraries(test_pair_map tc GTest::gtest_main)
add_executable(test_solve test_solve.cpp)
target_link_libraries(test_solve tc GTest::gtest_main)
@@ -17,6 +14,5 @@ target_compile_definitions(
MIN_COS_PER_SEC=$<IF:$<CONFIG:Debug>,${MIN_DEBUG_CPS},${MIN_RELEASE_CPS}>
)
gtest_discover_tests(test_pair_map)
gtest_discover_tests(test_solve)
gtest_discover_tests(test_lang)

View File

@@ -1,4 +1,4 @@
#include <tc/group.hpp>
#include <tc/core.hpp>
#include <tc/groups.hpp>
#include <gtest/gtest.h>
@@ -20,24 +20,33 @@
TEST(coxeter, simple) {
auto g = tc::coxeter("5 3 3");
ASSERT_EQ(g.rank, 4);
ASSERT_EQ(g.rank(), 4);
EXPECT_EQ(g.get(0, 0), 1);
EXPECT_EQ(g.get(0, 1), 5);
EXPECT_EQ(g.get(1, 2), 3);
EXPECT_EQ(g.get(3, 4), 3);
EXPECT_EQ(g.get(0, 2), 2);
EXPECT_EQ(g.get(0, 3), 2);
EXPECT_EQ(g.get(0, 4), 2);
EXPECT_EQ(g.get(1, 0), 5);
EXPECT_EQ(g.get(1, 1), 1);
EXPECT_EQ(g.get(1, 2), 3);
EXPECT_EQ(g.get(1, 3), 2);
EXPECT_EQ(g.get(1, 4), 2);
EXPECT_EQ(g.get(2, 4), 2);
EXPECT_EQ(g.get(2, 0), 2);
EXPECT_EQ(g.get(2, 1), 3);
EXPECT_EQ(g.get(2, 2), 1);
EXPECT_EQ(g.get(2, 3), 3);
EXPECT_EQ(g.get(3, 0), 2);
EXPECT_EQ(g.get(3, 1), 2);
EXPECT_EQ(g.get(3, 2), 3);
EXPECT_EQ(g.get(3, 3), 1);
}
TEST(coxeter, looping) {
auto g = tc::coxeter("{5 3 4}");
ASSERT_EQ(g.rank, 3);
ASSERT_EQ(g.rank(), 3);
EXPECT_EQ(g.get(0, 1), 5);
EXPECT_EQ(g.get(1, 2), 3);

View File

@@ -1,127 +0,0 @@
#include <functional>
#include <iostream>
#include <unordered_map>
#include <tc/pair_map.hpp>
#include <gtest/gtest.h>
/// helper for comparing against two options
template<typename T, typename U, typename V>
testing::AssertionResult AssertEqEither(
const char *val_expr,
const char *o1_expr,
const char *o2_expr,
T val, U o1, V o2
) {
if ((val == o1) || (val == o2)) {
return testing::AssertionSuccess();
}
return testing::AssertionFailure()
<< val_expr << " (" << val << ") " << "does not equal " << o1_expr
<< " (" << o1 << ") " << "or " << o2_expr << " (" << o2 << ")";
}
#define EXPECT_EQ_EITHER(val, o1, o2) EXPECT_PRED_FORMAT3(AssertEqEither, val, o1, o2)
/// Naive symmetric pair hash
size_t key(size_t i, size_t j) {
return ((i + j) << 12) ^ i ^ j;
}
/// factory to build a simple pair_map
tc::pair_map<size_t> populate(size_t size) {
tc::pair_map<size_t> pm(6);
for (int i = 0; i < pm.size(); ++i) {
for (int j = i; j < pm.size(); ++j) {
pm(i, j) = key(i, j);
}
}
return pm;
}
TEST(pair_map, fill) {
tc::pair_map<size_t> pm(6, 42);
for (int i = 0; i < pm.size(); ++i) {
for (int j = i; j < pm.size(); ++j) {
EXPECT_EQ(pm(i, j), 42);
}
}
}
TEST(pair_map, symmetry) {
auto pm = populate(6);
for (int i = 0; i < pm.size(); ++i) {
for (int j = i; j < pm.size(); ++j) {
EXPECT_EQ(pm(i, j), key(i, j));
EXPECT_EQ(pm(j, i), pm(i, j));
}
}
}
TEST(pair_map, copy) {
auto pm = populate(6);
auto pm_ = pm;
ASSERT_EQ(pm_.size(), 6);
for (int i = 0; i < pm_.size(); ++i) {
for (int j = i; j < pm_.size(); ++j) {
EXPECT_EQ(pm_(i, j), pm(i, j));
EXPECT_EQ(pm_(i, j), key(i, j));
}
}
}
TEST(pair_map, move) {
auto pm = populate(6);
auto pm_ = std::move(pm);
ASSERT_EQ(pm_.size(), 6);
for (int i = 0; i < pm_.size(); ++i) {
for (int j = i; j < pm_.size(); ++j) {
EXPECT_EQ(pm_(i, j), key(i, j));
}
}
}
TEST(pair_map, iterate) {
auto pm = populate(6);
size_t count = 0;
for (const auto &[i, j, m]: pm) {
EXPECT_EQ(m, key(i, j));
count++;
}
EXPECT_EQ(count, 21);
}
TEST(pair_map, iterate_ref) {
auto pm = populate(6);
for (const auto &[i, j, m]: pm) {
m = 42;
}
for (const auto &[i, j, m]: pm) {
EXPECT_EQ(m, 42);
}
}
TEST(pair_map, view) {
auto pm = populate(6);
size_t count = 0;
for (const auto &[i, j, m]: pm.of(4)) {
EXPECT_EQ_EITHER(4, i, j);
count++;
}
EXPECT_EQ(count, pm.size());
}

View File

@@ -11,15 +11,15 @@ testing::AssertionResult AssertSolveOrder(
const char *group_expr,
const char *sub_gens_expr,
const char *expected_order_expr,
const tc::Group &group,
const std::vector<tc::Gen> &sub_gens,
tc::Coset expected_order
const tc::Group<> &group,
const std::vector<size_t> &sub_gens,
size_t expected_order
) {
auto start = std::clock();
auto cosets = tc::solve(group, sub_gens);
auto cosets = group.solve(sub_gens);
auto end = std::clock();
tc::Coset actual_order = cosets.size();
size_t actual_order = cosets.order();
auto total_sec = (double) (end - start) / CLOCKS_PER_SEC;
auto cosets_per_sec = (double) actual_order / total_sec;
@@ -48,45 +48,45 @@ testing::AssertionResult AssertSolveOrder(
#define EXPECT_SOLVE_ORDER(group, sub_gens, expected_order) EXPECT_PRED_FORMAT3(AssertSolveOrder, group, sub_gens, expected_order);
using v = std::vector<tc::Gen>;
using v = std::vector<size_t>;
tc::Group A(unsigned int n) {
tc::Group<> A(unsigned int n) {
return tc::vcoxeter("3 * {}", {n - 1});
}
tc::Group B(unsigned int n) {
tc::Group<> B(unsigned int n) {
return tc::vcoxeter("4 3 * {}", {n - 2});
}
tc::Group D(unsigned int n) {
tc::Group<> D(unsigned int n) {
return tc::vcoxeter("3 * [1 1 {}]", {n - 3});
}
tc::Group E(unsigned int n) {
tc::Group<> E(unsigned int n) {
return tc::vcoxeter("3 * [1 2 {}]", {n - 4});
}
tc::Group F4() {
tc::Group<> F4() {
return tc::coxeter("3 4 3");
}
tc::Group G2() {
tc::Group<> G2() {
return tc::coxeter("6");
}
tc::Group H(unsigned int n) {
tc::Group<> H(unsigned int n) {
return tc::vcoxeter("5 3 * {}", {n - 2});
}
tc::Group I2(unsigned int n) {
tc::Group<> I2(unsigned int n) {
return tc::vcoxeter("{}", {n});
}
tc::Group T(unsigned int m, unsigned int n) {
tc::Group<> T(unsigned int m, unsigned int n) {
return tc::vcoxeter("{} 2 {}", {m, n});
}
tc::Group T(unsigned int n) {
tc::Group<> T(unsigned int n) {
return T(n, n);
}