Add tiny groups library with tests
This commit is contained in:
parent
3ed7f142d7
commit
4c5d8777d6
5 changed files with 413 additions and 0 deletions
7
libs/group/CMakeLists.txt
Normal file
7
libs/group/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
file(GLOB_RECURSE PSEMEK_GROUP_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp")
|
||||||
|
|
||||||
|
psemek_add_library(psemek-group ${PSEMEK_GROUP_HEADERS})
|
||||||
|
target_include_directories(psemek-group PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||||
|
target_link_libraries(psemek-group PUBLIC psemek-util)
|
||||||
|
|
||||||
|
psemek_glob_tests(psemek-group tests)
|
||||||
124
libs/group/include/psemek/group/cyclic.hpp
Normal file
124
libs/group/include/psemek/group/cyclic.hpp
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/util/range.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace psemek::group
|
||||||
|
{
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr = std::size_t>
|
||||||
|
struct cyclic
|
||||||
|
{
|
||||||
|
static_assert(std::is_integral_v<Repr> && std::is_unsigned_v<Repr>);
|
||||||
|
static_assert(std::numeric_limits<Repr>::max() > N);
|
||||||
|
|
||||||
|
static constexpr std::size_t size()
|
||||||
|
{
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
cyclic() = default;
|
||||||
|
|
||||||
|
static cyclic identity()
|
||||||
|
{
|
||||||
|
return cyclic{};
|
||||||
|
}
|
||||||
|
|
||||||
|
static cyclic rotation(Repr const & value)
|
||||||
|
{
|
||||||
|
return cyclic{value % static_cast<Repr>(N)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
static cyclic rotation(T const & value)
|
||||||
|
{
|
||||||
|
if (value >= 0)
|
||||||
|
return cyclic{static_cast<Repr>(static_cast<std::size_t>(value) % N)};
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto r = (static_cast<std::int64_t>(value) % static_cast<std::int64_t>(N));
|
||||||
|
return cyclic{static_cast<Repr>((r < 0) ? r + static_cast<std::int64_t>(N) : r)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repr value() const
|
||||||
|
{
|
||||||
|
return repr_;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct value_iterator
|
||||||
|
{
|
||||||
|
using difference_type = int;
|
||||||
|
using value_type = cyclic<N, Repr>;
|
||||||
|
using pointer = value_type *;
|
||||||
|
using reference = value_type &;
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
|
||||||
|
Repr repr;
|
||||||
|
|
||||||
|
cyclic<N, Repr> operator *() const
|
||||||
|
{
|
||||||
|
return cyclic<N, Repr>{repr};
|
||||||
|
}
|
||||||
|
|
||||||
|
value_iterator & operator++()
|
||||||
|
{
|
||||||
|
++repr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator == (value_iterator const & it1, value_iterator const & it2)
|
||||||
|
{
|
||||||
|
return it1.repr == it2.repr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto values()
|
||||||
|
{
|
||||||
|
return util::range{value_iterator{0}, value_iterator{N}};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Repr repr_{0};
|
||||||
|
|
||||||
|
explicit cyclic(Repr const & repr)
|
||||||
|
: repr_(repr)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
bool operator == (cyclic<N, Repr> const & g1, cyclic<N, Repr> const & g2)
|
||||||
|
{
|
||||||
|
return g1.value() == g2.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
auto operator <=> (cyclic<N, Repr> const & g1, cyclic<N, Repr> const & g2)
|
||||||
|
{
|
||||||
|
return g1.value() <=> g2.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
cyclic<N, Repr> operator * (cyclic<N, Repr> const & g1, cyclic<N, Repr> const & g2)
|
||||||
|
{
|
||||||
|
return cyclic<N, Repr>::rotation(g1.value() + g2.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
cyclic<N, Repr> inverse(cyclic<N, Repr> const & g)
|
||||||
|
{
|
||||||
|
return cyclic<N, Repr>::rotation(N - g.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OStream, std::size_t N, typename Repr>
|
||||||
|
OStream & operator << (OStream & os, cyclic<N, Repr> const & g)
|
||||||
|
{
|
||||||
|
os << 'C' << N << "(r" << static_cast<std::size_t>(g.value()) << ")";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
177
libs/group/include/psemek/group/dihedral.hpp
Normal file
177
libs/group/include/psemek/group/dihedral.hpp
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/group/cyclic.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace psemek::group
|
||||||
|
{
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr = std::size_t>
|
||||||
|
struct dihedral
|
||||||
|
{
|
||||||
|
static_assert(std::is_integral_v<Repr> && std::is_unsigned_v<Repr>);
|
||||||
|
static_assert(std::numeric_limits<Repr>::max() > N);
|
||||||
|
|
||||||
|
static constexpr std::size_t size()
|
||||||
|
{
|
||||||
|
return 2 * N;
|
||||||
|
}
|
||||||
|
|
||||||
|
dihedral() = default;
|
||||||
|
|
||||||
|
static dihedral identity()
|
||||||
|
{
|
||||||
|
return dihedral{};
|
||||||
|
}
|
||||||
|
|
||||||
|
static dihedral rotation(Repr const & repr)
|
||||||
|
{
|
||||||
|
return dihedral{repr % static_cast<Repr>(N)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
static dihedral rotation(T const & value)
|
||||||
|
{
|
||||||
|
if (value >= 0)
|
||||||
|
return dihedral{static_cast<Repr>(static_cast<std::size_t>(value) % N)};
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto r = (static_cast<std::int64_t>(value) % static_cast<std::int64_t>(N));
|
||||||
|
return dihedral{static_cast<Repr>((r < 0) ? r + static_cast<std::int64_t>(N) : r)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Repr2>
|
||||||
|
static dihedral rotation(cyclic<N, Repr2> const & value)
|
||||||
|
{
|
||||||
|
return dihedral{static_cast<Repr>(value.repr())};
|
||||||
|
}
|
||||||
|
|
||||||
|
static dihedral reflection(Repr repr)
|
||||||
|
{
|
||||||
|
return dihedral{(repr % static_cast<Repr>(N)) + static_cast<Repr>(N)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
|
||||||
|
static dihedral reflection(T const & value)
|
||||||
|
{
|
||||||
|
if (value >= 0)
|
||||||
|
return dihedral{static_cast<Repr>((static_cast<std::size_t>(value) % N) + N)};
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto r = (static_cast<std::int64_t>(value) % static_cast<std::int64_t>(N));
|
||||||
|
return dihedral{static_cast<Repr>((r < 0) ? r + static_cast<std::int64_t>(2 * N) : r + static_cast<std::int64_t>(N))};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repr value() const
|
||||||
|
{
|
||||||
|
return repr_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_rotation() const
|
||||||
|
{
|
||||||
|
return repr_ < N;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_reflection() const
|
||||||
|
{
|
||||||
|
return repr_ >= N;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct value_iterator
|
||||||
|
{
|
||||||
|
using difference_type = int;
|
||||||
|
using value_type = dihedral<N, Repr>;
|
||||||
|
using pointer = value_type *;
|
||||||
|
using reference = value_type &;
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
|
||||||
|
Repr repr;
|
||||||
|
|
||||||
|
dihedral<N, Repr> operator *() const
|
||||||
|
{
|
||||||
|
return dihedral<N, Repr>{repr};
|
||||||
|
}
|
||||||
|
|
||||||
|
value_iterator & operator++()
|
||||||
|
{
|
||||||
|
++repr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend bool operator == (value_iterator const & it1, value_iterator const & it2)
|
||||||
|
{
|
||||||
|
return it1.repr == it2.repr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto values()
|
||||||
|
{
|
||||||
|
return util::range{value_iterator{0}, value_iterator{2 * N}};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Repr repr_{0};
|
||||||
|
|
||||||
|
explicit dihedral(Repr const & repr)
|
||||||
|
: repr_(repr)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
bool operator == (dihedral<N, Repr> const & g1, dihedral<N, Repr> const & g2)
|
||||||
|
{
|
||||||
|
return g1.value() == g2.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
auto operator <=> (dihedral<N, Repr> const & g1, dihedral<N, Repr> const & g2)
|
||||||
|
{
|
||||||
|
return g1.value() <=> g2.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
dihedral<N, Repr> operator * (dihedral<N, Repr> const & g1, dihedral<N, Repr> const & g2)
|
||||||
|
{
|
||||||
|
if (g1.is_rotation())
|
||||||
|
{
|
||||||
|
auto repr = g1.value() + g2.value();
|
||||||
|
if (g2.is_rotation())
|
||||||
|
return dihedral<N, Repr>::rotation(repr);
|
||||||
|
else
|
||||||
|
return dihedral<N, Repr>::reflection(repr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto repr = g1.value() + static_cast<Repr>(N) - g2.value();
|
||||||
|
if (g2.is_rotation())
|
||||||
|
return dihedral<N, Repr>::reflection(repr);
|
||||||
|
else
|
||||||
|
return dihedral<N, Repr>::rotation(repr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N, typename Repr>
|
||||||
|
dihedral<N, Repr> inverse(dihedral<N, Repr> const & g)
|
||||||
|
{
|
||||||
|
if (g.is_rotation())
|
||||||
|
return dihedral<N, Repr>::rotation(N - g.value());
|
||||||
|
else
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OStream, std::size_t N, typename Repr>
|
||||||
|
OStream & operator << (OStream & os, dihedral<N, Repr> const & g)
|
||||||
|
{
|
||||||
|
if (g.is_rotation())
|
||||||
|
os << 'D' << N << "(r" << static_cast<std::size_t>(g.value()) << ")";
|
||||||
|
else
|
||||||
|
os << 'D' << N << "(s" << (static_cast<std::size_t>(g.value()) % N) << ")";
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
43
libs/group/tests/cyclic.cpp
Normal file
43
libs/group/tests/cyclic.cpp
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#include <psemek/test/test.hpp>
|
||||||
|
|
||||||
|
#include <psemek/group/cyclic.hpp>
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
using namespace psemek::group;
|
||||||
|
|
||||||
|
using group_type = cyclic<7, std::uint8_t>;
|
||||||
|
|
||||||
|
test_case(group_cyclic_construct)
|
||||||
|
{
|
||||||
|
expect_equal(group_type::identity().value(), 0);
|
||||||
|
|
||||||
|
int const n = group_type::size();
|
||||||
|
|
||||||
|
for (int i = 0; i < 4 * n; ++i)
|
||||||
|
expect_equal(group_type::rotation(i).value(), i % n);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4 * n; ++i)
|
||||||
|
expect_equal(group_type::rotation(i - 4 * n).value(), i % n);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(group_cyclic_values)
|
||||||
|
{
|
||||||
|
std::set<group_type> values;
|
||||||
|
for (auto g : group_type::values())
|
||||||
|
values.insert(g);
|
||||||
|
expect_equal(values.size(), group_type::size());
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(group_cyclic_multiply)
|
||||||
|
{
|
||||||
|
for (auto g1 : group_type::values())
|
||||||
|
for (auto g2 : group_type::values())
|
||||||
|
expect_equal(g1 * g2, group_type::rotation(g1.value() + g2.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(group_cyclic_inverse)
|
||||||
|
{
|
||||||
|
for (auto g : group_type::values())
|
||||||
|
expect_equal(g * inverse(g), group_type::identity());
|
||||||
|
}
|
||||||
62
libs/group/tests/dihedral.cpp
Normal file
62
libs/group/tests/dihedral.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#include <psemek/test/test.hpp>
|
||||||
|
|
||||||
|
#include <psemek/group/dihedral.hpp>
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
using namespace psemek::group;
|
||||||
|
|
||||||
|
using group_type = dihedral<7, std::uint8_t>;
|
||||||
|
|
||||||
|
test_case(group_dihedral_construct)
|
||||||
|
{
|
||||||
|
expect_equal(group_type::identity().value(), 0);
|
||||||
|
|
||||||
|
int const n = group_type::size() / 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4 * n; ++i)
|
||||||
|
expect_equal(group_type::rotation(i).value(), i % n);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4 * n; ++i)
|
||||||
|
expect_equal(group_type::reflection(i).value(), (i % n) + n);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4 * n; ++i)
|
||||||
|
expect_equal(group_type::rotation(i - 4 * n).value(), i % n);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4 * n; ++i)
|
||||||
|
expect_equal(group_type::reflection(i - 4 * n).value(), (i % n) + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(group_dihedral_values)
|
||||||
|
{
|
||||||
|
std::set<group_type> values;
|
||||||
|
for (auto g : group_type::values())
|
||||||
|
values.insert(g);
|
||||||
|
expect_equal(values.size(), group_type::size());
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(group_dihedral_multiply)
|
||||||
|
{
|
||||||
|
for (auto g1 : group_type::values())
|
||||||
|
{
|
||||||
|
for (auto g2 : group_type::values())
|
||||||
|
{
|
||||||
|
auto value = g1.is_rotation() ? g1.value() + g2.value() : g1.value() + group_type::size() / 2 - g2.value();
|
||||||
|
|
||||||
|
if (g1.is_reflection() ^ g2.is_reflection())
|
||||||
|
{
|
||||||
|
expect_equal(g1 * g2, group_type::reflection(value));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
expect_equal(g1 * g2, group_type::rotation(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case(group_dihedral_inverse)
|
||||||
|
{
|
||||||
|
for (auto g : group_type::values())
|
||||||
|
expect_equal(g * inverse(g), group_type::identity());
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue