diff --git a/libs/group/CMakeLists.txt b/libs/group/CMakeLists.txt new file mode 100644 index 00000000..7a2b0a31 --- /dev/null +++ b/libs/group/CMakeLists.txt @@ -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) diff --git a/libs/group/include/psemek/group/cyclic.hpp b/libs/group/include/psemek/group/cyclic.hpp new file mode 100644 index 00000000..601d3340 --- /dev/null +++ b/libs/group/include/psemek/group/cyclic.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include + +#include +#include +#include +#include + +namespace psemek::group +{ + + template + struct cyclic + { + static_assert(std::is_integral_v && std::is_unsigned_v); + static_assert(std::numeric_limits::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(N)}; + } + + template >> + static cyclic rotation(T const & value) + { + if (value >= 0) + return cyclic{static_cast(static_cast(value) % N)}; + else + { + auto r = (static_cast(value) % static_cast(N)); + return cyclic{static_cast((r < 0) ? r + static_cast(N) : r)}; + } + } + + Repr value() const + { + return repr_; + } + + struct value_iterator + { + using difference_type = int; + using value_type = cyclic; + using pointer = value_type *; + using reference = value_type &; + using iterator_category = std::forward_iterator_tag; + + Repr repr; + + cyclic operator *() const + { + return cyclic{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 + bool operator == (cyclic const & g1, cyclic const & g2) + { + return g1.value() == g2.value(); + } + + template + auto operator <=> (cyclic const & g1, cyclic const & g2) + { + return g1.value() <=> g2.value(); + } + + template + cyclic operator * (cyclic const & g1, cyclic const & g2) + { + return cyclic::rotation(g1.value() + g2.value()); + } + + template + cyclic inverse(cyclic const & g) + { + return cyclic::rotation(N - g.value()); + } + + template + OStream & operator << (OStream & os, cyclic const & g) + { + os << 'C' << N << "(r" << static_cast(g.value()) << ")"; + return os; + } + +} diff --git a/libs/group/include/psemek/group/dihedral.hpp b/libs/group/include/psemek/group/dihedral.hpp new file mode 100644 index 00000000..33899469 --- /dev/null +++ b/libs/group/include/psemek/group/dihedral.hpp @@ -0,0 +1,177 @@ +#pragma once + +#include + +#include +#include +#include + +namespace psemek::group +{ + + template + struct dihedral + { + static_assert(std::is_integral_v && std::is_unsigned_v); + static_assert(std::numeric_limits::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(N)}; + } + + template >> + static dihedral rotation(T const & value) + { + if (value >= 0) + return dihedral{static_cast(static_cast(value) % N)}; + else + { + auto r = (static_cast(value) % static_cast(N)); + return dihedral{static_cast((r < 0) ? r + static_cast(N) : r)}; + } + } + + template + static dihedral rotation(cyclic const & value) + { + return dihedral{static_cast(value.repr())}; + } + + static dihedral reflection(Repr repr) + { + return dihedral{(repr % static_cast(N)) + static_cast(N)}; + } + + template >> + static dihedral reflection(T const & value) + { + if (value >= 0) + return dihedral{static_cast((static_cast(value) % N) + N)}; + else + { + auto r = (static_cast(value) % static_cast(N)); + return dihedral{static_cast((r < 0) ? r + static_cast(2 * N) : r + static_cast(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; + using pointer = value_type *; + using reference = value_type &; + using iterator_category = std::forward_iterator_tag; + + Repr repr; + + dihedral operator *() const + { + return dihedral{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 + bool operator == (dihedral const & g1, dihedral const & g2) + { + return g1.value() == g2.value(); + } + + template + auto operator <=> (dihedral const & g1, dihedral const & g2) + { + return g1.value() <=> g2.value(); + } + + template + dihedral operator * (dihedral const & g1, dihedral const & g2) + { + if (g1.is_rotation()) + { + auto repr = g1.value() + g2.value(); + if (g2.is_rotation()) + return dihedral::rotation(repr); + else + return dihedral::reflection(repr); + } + else + { + auto repr = g1.value() + static_cast(N) - g2.value(); + if (g2.is_rotation()) + return dihedral::reflection(repr); + else + return dihedral::rotation(repr); + } + } + + template + dihedral inverse(dihedral const & g) + { + if (g.is_rotation()) + return dihedral::rotation(N - g.value()); + else + return g; + } + + template + OStream & operator << (OStream & os, dihedral const & g) + { + if (g.is_rotation()) + os << 'D' << N << "(r" << static_cast(g.value()) << ")"; + else + os << 'D' << N << "(s" << (static_cast(g.value()) % N) << ")"; + return os; + } + +} diff --git a/libs/group/tests/cyclic.cpp b/libs/group/tests/cyclic.cpp new file mode 100644 index 00000000..39547a68 --- /dev/null +++ b/libs/group/tests/cyclic.cpp @@ -0,0 +1,43 @@ +#include + +#include + +#include + +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 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()); +} diff --git a/libs/group/tests/dihedral.cpp b/libs/group/tests/dihedral.cpp new file mode 100644 index 00000000..580bdd79 --- /dev/null +++ b/libs/group/tests/dihedral.cpp @@ -0,0 +1,62 @@ +#include + +#include + +#include + +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 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()); +}