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