// // libsemigroups - C++ library for semigroups and monoids // Copyright (C) 2019 James D. Mitchell // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. //
// This file contains a generic implementation of a class Action which // represents the action of a semigroup on a set.
#include <cstddef> // for size_t #include <type_traits> // for is_trivially_default_construc... #include <unordered_map> // for unordered_map #include <vector> // for vector
#include"adapters.hpp"// for One #include"bruidhinn-traits.hpp"// for detail::BruidhinnTraits #include"debug.hpp"// for LIBSEMIGROUPS_ASSERT #include"digraph.hpp"// for ActionDigraph #include"exception.hpp"// for LIBSEMIGROUPS_EXCEPTION #include"report.hpp"// for REPORT_DEFAULT #include"runner.hpp"// for Runner
namespace libsemigroups { //! The values in this enum can be used as a template parameter for the Action //! class to specify whether the action of the Action is a left or right //! action. enumclass side { //! This value indicates that the action in an Action instance should be a //! left action.
left, //! This value indicates that the action in an Action instance should be a //! right action.
right
};
//! Defined in ``action.hpp``. //! //! This page contains details of the Action class in ``libsemigroups`` for //! finding actions of semigroups, or groups, on sets. The notion of an //! "action" in the context of ``libsemigroups`` is analogous to the notion //! of an orbit of a group. //! //! You are unlikely to want to use this class directly directly, but rather //! via the more convenient aliases \ref RightAction and //! \ref LeftAction. //! //! The function \c run finds points //! that can be obtained by acting on the seeds of \c this by the generators //! of \c this until no further points can be found, or \c stopped //! returns \c true. This is achieved by performing a breadth first search. //! //! \tparam TElementType the type of the elements of the semigroup. //! //! \tparam TPointType the type of the points acted on. //! //! \tparam TActionType the type of the action of elements of type //! `TElementType` on points of type `TPointType`. This type should be a //! stateless trivially default constructible class with a call operator of //! signature `void(TPointType&, TPointType const&, TElementType const&)` //! which computes the action of the 3rd argument on the 2nd argument, and //! stores it in the 1st argument. See, for example, //! ImageLeftAction and ImageRightAction. //! //! \tparam TTraits the type of a traits class with the requirements of //! \ref ActionTraits. //! //! \tparam TLeftOrRight the libsemigroups::side of the action (i.e. if it is //! is a left or a right action). //! //! \sa libsemigroups::side, ActionTraits, LeftAction, and RightAction. //! //! \par Example //! \code //! using namespace libsemigroups; //! auto rg = ReportGuard(REPORT); //! RightAction<PPerm<16>, PPerm<16>, ImageRightAction<PPerm<16>, PPerm<16>>> //! o; o.add_seed(PPerm<16>::identity(16)); o.add_generator( //! PPerm<16>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, //! {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0}, //! 16)); //! o.add_generator( //! PPerm<16>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, //! {1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, //! 16)); //! o.add_generator( //! PPerm<16>({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, //! {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, //! 16)); //! o.add_generator( //! PPerm<16>({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, //! {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, //! 16)); //! o.reserve(70000); //! o.size(); // 65536 //! o.digraph().number_of_scc(); // 17 //! \endcode //! //! \complexity //! The time complexity is \f$O(mn)\f$ where \f$m\f$ is the total //! number of points in the orbit and \f$n\f$ is the number of generators. template <typename TElementType, typename TPointType, typename TActionType, typename TTraits,
side TLeftOrRight> class Action : public Runner, private detail::BruidhinnTraits<TPointType> { //////////////////////////////////////////////////////////////////////// // Action - typedefs - private ////////////////////////////////////////////////////////////////////////
using internal_point_type = typename detail::BruidhinnTraits<TPointType>::internal_value_type; using internal_const_point_type = typename detail::BruidhinnTraits<TPointType>::internal_const_value_type; using internal_const_reference = typename detail::BruidhinnTraits<TPointType>::internal_const_reference;
static_assert(
std::is_const<internal_const_point_type>::value
|| std::is_const<typename std::remove_pointer<
internal_const_point_type>::type>::value, "internal_const_element_type must be const or pointer to const");
public: //////////////////////////////////////////////////////////////////////// // Action - typedefs - public ////////////////////////////////////////////////////////////////////////
//! The template parameter \p TElementType. //! //! Also the type of the elements of the semigroup. using element_type = TElementType;
//! The template parameter \p TPointType. //! //! Also the type of the points acted on by this class. using point_type = TPointType;
//! The type of a const reference to a \ref point_type. using const_reference_point_type = typename detail::BruidhinnTraits<TPointType>::const_reference;
//! The type of a const pointer to a \ref point_type. using const_pointer_point_type = typename detail::BruidhinnTraits<TPointType>::const_pointer;
//! The type of the index of a point. using index_type = size_t;
//! The type of the index of a strongly connected component. //! //! \sa ActionDigraph::scc_index_type using scc_index_type = ActionDigraph<size_t>::scc_index_type;
//! Type of the action of \ref element_type on \ref point_type. //! //! \sa ImageRightAction, ImageLeftAction using action_type = TActionType;
//////////////////////////////////////////////////////////////////////// // Action - iterators - public ////////////////////////////////////////////////////////////////////////
//! The type of a const iterator pointing to a single strongly //! connected component (scc). using const_iterator_scc = ActionDigraph<size_t>::const_iterator_scc;
//! The type of a const iterator pointing to a strongly //! connected component (scc). using const_iterator_sccs = ActionDigraph<size_t>::const_iterator_sccs;
//! The type of a const iterator pointing to a representative of a //! strongly connected component (scc). using const_iterator_scc_roots
= ActionDigraph<size_t>::const_iterator_scc_roots;
//! The type of a const iterator pointing to a \ref point_type. using const_iterator
= detail::BruidhinnConstIterator<point_type,
std::vector<internal_point_type>>;
using ActionOp = TActionType; using One = typename TTraits::One; using Product = typename TTraits::Product; using Swap = typename TTraits::Swap;
static_assert(std::is_trivially_default_constructible<ActionOp>::value, "the third template parameter TActionType is not trivially " "default constructible"); // TODO(later) more static assertions
~Action() { if (_tmp_point_init) {
this->internal_free(_tmp_point);
} for (auto pt : _orb) {
this->internal_free(pt);
}
}
//////////////////////////////////////////////////////////////////////// // Action - modifiers - public ////////////////////////////////////////////////////////////////////////
//! Increase the capacity to a value that is greater or equal to \p val. //! //! \param val new capacity of an action instance. //! //! \returns (None) //! //! \throws std::length_error if \p val is too large. //! //! \throws std::bad_alloc or any exception thrown by the allocators of //! private data members. //! //! \complexity //! At most linear in the size() of the Action. void reserve(size_t val) {
_graph.reserve(val, _gens.size());
_map.reserve(val);
_orb.reserve(val);
}
//! Add a seed to the action. //! //! A *seed* is just a starting point for the action, it will belong to the //! action, as will every point that can be obtained from the seed by //! acting with the generators of the action. //! //! \param seed the seed to add. //! //! \returns (None) //! //! \exceptions //! \no_libsemigroups_except //! //! \complexity //! At most linear in the size() of the action. void add_seed(const_reference_point_type seed) { auto internal_seed = this->internal_copy(this->to_internal_const(seed)); if (!_tmp_point_init) {
_tmp_point_init = true;
_tmp_point = this->internal_copy(internal_seed);
}
_map.emplace(internal_seed, _orb.size());
_orb.push_back(internal_seed);
_graph.add_nodes(1);
}
//! Add a generator to the action. An Action instance represents the //! action of the semigroup generated by the elements added via this //! member function. //! //! \param gen the generator to add. //! //! \returns (None) //! //! \exceptions //! \no_libsemigroups_except //! //! \complexity //! At most linear in the size() of the action. void add_generator(element_type gen) {
_gens.push_back(gen);
}
//////////////////////////////////////////////////////////////////////// // Action - member functions: position, empty, size, etc - public ////////////////////////////////////////////////////////////////////////
//! Returns the position of a point in the so far discovered //! points. //! //! \param pt the point whose position is sought. //! //! \returns The index of \p pt in \c this or \ref UNDEFINED. //! //! \exceptions //! \no_libsemigroups_except //! //! \complexity //! Constant.
index_type position(const_reference_point_type pt) const { auto it = _map.find(this->to_internal_const(pt)); if (it != _map.end()) { return (*it).second;
} else { return UNDEFINED;
}
}
//! Checks if the Action contains any points. //! //! \returns \c true if the action contains no points (including seeds), //! and \c false if not. //! //! \exceptions //! \noexcept //! //! \complexity //! Constant. //! //! \par Parameters //! (None) bool empty() const noexcept { return _orb.empty();
}
//! Returns a const reference to the point in a given position. //! //! \param pos the index of an element. //! //! \returns //! A \ref const_reference_point_type to the point in position //! \p pos of the currently enumerated points. //! //! \exceptions //! \noexcept //! //! \complexity //! Constant. inline const_reference_point_type operator[](size_t pos) const noexcept {
LIBSEMIGROUPS_ASSERT(pos < _orb.size()); return this->to_external_const(_orb[pos]);
}
//! Returns a const reference to the point in a given position. //! //! \param pos the index of an element. //! //! \returns //! A \ref const_reference_point_type to the point in position \p //! pos of the currently enumerated points. //! //! \throws //! std::out_of_range if `!(pos < current_size())`. //! //! \complexity //! Constant. inline const_reference_point_type at(size_t pos) const { return this->to_external_const(_orb.at(pos));
}
//! Returns the size of the fully enumerated action. //! //! \returns The size of the action, a value of type \c size_t. //! //! \complexity //! The time complexity is \f$O(mn)\f$ where \f$m\f$ is the total number of //! points in the orbit and \f$n\f$ is the number of generators. //! //! \exceptions //! \no_libsemigroups_except //! //! \par Parameters //! (None)
size_t size() {
run(); return _orb.size();
}
//! Returns the number of points found so far. //! //! \returns //! A value of \c size_t. //! //! \complexity //! Constant. //! //! \exceptions //! \noexcept //! //! \par Parameters //! (None)
size_t current_size() const noexcept { return _orb.size();
}
//! Returns a \ref const_iterator (random access //! iterator) pointing at the first point. //! //! \returns //! A const iterator to the first point. //! //! \complexity //! Constant. //! //! \exceptions //! \noexcept //! //! \par Parameters //! (None)
const_iterator cbegin() const noexcept { return const_iterator(_orb.cbegin());
}
//! Returns a \ref const_iterator (random access //! iterator) pointing one past the last point. //! //! \returns //! A const iterator to one past the end. //! //! \complexity //! Constant. //! //! \exceptions //! \noexcept //! //! \par Parameters //! (None)
const_iterator cend() const noexcept { return const_iterator(_orb.cend());
}
//////////////////////////////////////////////////////////////////////// // Action - member functions: strongly connected components - public ////////////////////////////////////////////////////////////////////////
//! Returns whether or not we are caching scc multipliers. //! //! If the returned value of this function is \c true, then the values //! returned by multiplier_from_scc_root() and multiplier_to_scc_root() are //! cached, and not recomputed every time one of these functions is called. //! //! \returns //! A value of type \c bool. //! //! \complexity //! Constant. //! //! \exceptions //! \noexcept //! //! \par Parameters //! (None) bool cache_scc_multipliers() const noexcept { return _options._cache_scc_multipliers;
}
//! Set whether or not to cache scc multipliers. //! //! If the parameter \p val is \c true, then the values returned by //! multiplier_from_scc_root() and multiplier_to_scc_root() are cached, and //! not recomputed every time one of these functions is called. //! //! \param val the value. //! //! \returns //! A reference to \c this. //! //! \complexity //! Constant. //! //! \exceptions //! \noexcept
Action& cache_scc_multipliers(bool val) noexcept {
_options._cache_scc_multipliers = val; return *this;
}
//! Returns a multiplier from a scc root to a given index. //! //! Returns an element \c x of the semigroup generated by the generators //! in the action such that if \c r is the root of the strongly connected //! component containing \c at(pos), then after `TActionType()(res, r, //! x)` the point \c res equals \c at(pos). //! //! \param pos a position in the action. //! //! \returns An element of type \ref element_type. //! //! \complexity //! At most \f$O(mn)\f$ where \f$m\f$ is the complexity of multiplying //! elements of type \ref element_type and \f$n\f$ is the size of the fully //! enumerated orbit. //! //! \throws LibsemigroupsException if there are no generators yet added //! or the index \p pos is out of range.
element_type multiplier_from_scc_root(index_type pos) {
validate_gens();
validate_index(pos); if (cache_scc_multipliers()) { if (_multipliers_from_scc_root.defined(pos)) { return _multipliers_from_scc_root[pos];
}
_multipliers_from_scc_root.init(_graph.number_of_nodes(), _gens[0]);
index_type i = pos;
std::stack<index_type> visited; while (!_multipliers_from_scc_root.defined(i)
&& _graph.spanning_forest().parent(i) != UNDEFINED) {
visited.push(i);
_multipliers_from_scc_root[i]
= _gens[_graph.spanning_forest().label(i)];
i = _graph.spanning_forest().parent(i);
} if (visited.empty()) { // if pos is the scc root, then this can happen
_multipliers_from_scc_root.set_defined(pos);
} else {
element_type tmp = One()(_gens[0]); while (i != pos) {
index_type j = visited.top();
visited.pop();
Swap()(tmp, _multipliers_from_scc_root[j]);
internal_product(_multipliers_from_scc_root[j],
_multipliers_from_scc_root[i],
tmp);
_multipliers_from_scc_root.set_defined(j);
i = j;
}
} return _multipliers_from_scc_root[pos];
} else {
element_type out = One()(_gens[0]);
element_type tmp = One()(_gens[0]); while (_graph.spanning_forest().parent(pos) != UNDEFINED) {
Swap()(tmp, out);
internal_product(
out, _gens[_graph.spanning_forest().label(pos)], tmp);
pos = _graph.spanning_forest().parent(pos);
} return out;
}
}
//! Returns a multiplier from a given index to a scc root. //! //! Returns an element \c x of the semigroup generated by the generators //! in the action such that after `TActionType()(res, at(pos), x)` //! the point \c res is the root of the strongly connected component //! containing \c at(pos). //! //! \param pos a position in the action. //! //! \returns An element of type \ref element_type. //! //! \complexity //! At most \f$O(mn)\f$ where \f$m\f$ is the complexity of multiplying //! elements of type \ref element_type and \f$n\f$ is the size of the fully //! enumerated orbit. //! //! \throws LibsemigroupsException if there are no generators yet added //! or the index \p pos is out of range.
element_type multiplier_to_scc_root(index_type pos) {
validate_gens();
validate_index(pos); if (cache_scc_multipliers()) { if (_multipliers_to_scc_root.defined(pos)) { return _multipliers_to_scc_root[pos];
}
_multipliers_to_scc_root.init(_graph.number_of_nodes(), _gens[0]);
index_type i = pos;
std::stack<index_type> visited; while (!_multipliers_to_scc_root.defined(i)
&& _graph.reverse_spanning_forest().parent(i) != UNDEFINED) {
visited.push(i);
_multipliers_to_scc_root[i]
= _gens[_graph.reverse_spanning_forest().label(i)];
i = _graph.reverse_spanning_forest().parent(i);
} if (visited.empty()) { // if pos is the scc root, then this can happen
_multipliers_to_scc_root.set_defined(pos);
} else {
element_type tmp = One()(_gens[0]); while (i != pos) {
index_type j = visited.top();
visited.pop();
Swap()(tmp, _multipliers_to_scc_root[j]);
internal_product(
_multipliers_to_scc_root[j], tmp, _multipliers_to_scc_root[i]);
_multipliers_to_scc_root.set_defined(j);
i = j;
}
} return _multipliers_to_scc_root[pos];
} else {
element_type out = One()(_gens[0]);
element_type tmp = One()(_gens[0]); while (_graph.reverse_spanning_forest().parent(pos) != UNDEFINED) {
Swap()(tmp, out);
internal_product(
out, tmp, _gens[_graph.reverse_spanning_forest().label(pos)]);
pos = _graph.reverse_spanning_forest().parent(pos);
} return out;
}
}
//! Returns a const reference to the root point of a strongly connected //! component. //! //! \param x the point whose root we want to find. //! //! \returns A value of type \ref const_reference_point_type. //! //! \complexity //! At most \f$O(mn)\f$ where \f$m\f$ is the complexity of multiplying //! elements of type \ref element_type and \f$n\f$ is the size of the fully //! enumerated orbit. //! //! \throws LibsemigroupsException if the point \p x does not belong to //! the action.
const_reference_point_type root_of_scc(const_reference_point_type x) { return this->to_external_const(_orb[_graph.root_of_scc(position(x))]);
}
//! Returns a const reference to the root point of a strongly connected //! component. //! //! \param pos the index of the point in the action whose root we want to //! find. //! //! \returns A value of type \ref const_reference_point_type. //! //! \complexity //! At most \f$O(mn)\f$ where \f$m\f$ is the complexity of multiplying //! elements of type \ref element_type and \f$n\f$ is the size of the fully //! enumerated orbit. //! //! \throws LibsemigroupsException if the index \p pos is out of range.
const_reference_point_type root_of_scc(index_type pos) { return this->to_external_const(_orb[_graph.root_of_scc(pos)]);
}
//! Returns the digraph of the completely enumerated action. //! //! \returns A const reference to an ActionDigraph<size_t>. //! //! \complexity //! At most \f$O(mn)\f$ where \f$m\f$ is the complexity of multiplying //! elements of type \ref element_type and \f$n\f$ is the size of the fully //! enumerated orbit. //! //! \exceptions //! \no_libsemigroups_except //! //! \par Parameters //! (None)
ActionDigraph<size_t> const& digraph() {
run(); return _graph;
}
private: //////////////////////////////////////////////////////////////////////// // Runner - pure virtual member functions - private ////////////////////////////////////////////////////////////////////////
void run_impl() override {
size_t old_nr_gens = _graph.out_degree();
_graph.add_to_out_degree(_gens.size() - _graph.out_degree()); if (started() && old_nr_gens < _gens.size()) { // Generators were added after the last call to run for (size_t i = 0; i < _pos; i++) { for (size_t j = old_nr_gens; j < _gens.size(); ++j) {
ActionOp()(this->to_external(_tmp_point),
this->to_external_const(_orb[i]),
_gens[j]); auto it = _map.find(_tmp_point); if (it == _map.end()) {
_graph.add_nodes(1);
_graph.add_edge(i, _orb.size(), j);
_orb.push_back(this->internal_copy(_tmp_point));
_map.emplace(_orb.back(), _orb.size() - 1);
} else {
_graph.add_edge(i, (*it).second, j);
}
}
}
}
for (; _pos < _orb.size() && !stopped(); ++_pos) { for (size_t j = 0; j < _gens.size(); ++j) {
ActionOp()(this->to_external(_tmp_point),
this->to_external_const(_orb[_pos]),
_gens[j]); auto it = _map.find(_tmp_point); if (it == _map.end()) {
_graph.add_nodes(1);
_graph.add_edge(_pos, _orb.size(), j);
_orb.push_back(this->internal_copy(_tmp_point));
_map.emplace(_orb.back(), _orb.size() - 1);
} else {
_graph.add_edge(_pos, (*it).second, j);
}
} if (report()) {
REPORT_DEFAULT("found %d points, so far\n", _orb.size());
}
}
report_why_we_stopped();
}
//////////////////////////////////////////////////////////////////////// // Action - member functions - private ////////////////////////////////////////////////////////////////////////
void validate_index(index_type i) const { if (i > _orb.size()) {
LIBSEMIGROUPS_EXCEPTION( "index out of range, expected value in [0, %d) but found %d",
current_size(),
i);
}
}
void validate_gens() const { if (_gens.empty()) {
LIBSEMIGROUPS_EXCEPTION("no generators defined, this methods cannot be " "used until at least one generator is added")
}
}
//! This is a traits class for use with Action, \ref LeftAction, //! and \ref RightAction. //! //! \tparam TElementType the type of the elements. //! \tparam TPointType the type of the points acted on. //! //! \sa Action. template <typename TElementType, typename TPointType> struct ActionTraits { //! \copydoc libsemigroups::Hash using Hash = ::libsemigroups::Hash<TPointType>; //! \copydoc libsemigroups::EqualTo using EqualTo = ::libsemigroups::EqualTo<TPointType>; //! \copydoc libsemigroups::Swap using Swap = ::libsemigroups::Swap<TElementType>; //! \copydoc libsemigroups::One using One = ::libsemigroups::One<TElementType>; //! \copydoc libsemigroups::Product using Product = ::libsemigroups::Product<TElementType>;
};
//! This class represents the right action of a semigroup on a set. //! //! \sa Action for further details. template <typename TElementType, typename TPointType, typename TActionType, typename TTraits = ActionTraits<TElementType, TPointType>> using RightAction
= Action<TElementType, TPointType, TActionType, TTraits, side::right>;
//! This class represents the left action of a semigroup on a set. //! //! \sa Action for further details. template <typename TElementType, typename TPointType, typename TActionType, typename TTraits = ActionTraits<TElementType, TPointType>> using LeftAction
= Action<TElementType, TPointType, TActionType, TTraits, side::left>;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.