Quelle sims1.tpp
Sprache: unbekannt
|
|
//
// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2022 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 declaration of a class for performing the "low-index
// congruence" algorithm for semigroups and monoid.
namespace libsemigroups {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
std::ostream& operator<<(std::ostream& os, Sims1Stats const& stats) {
os << "#0: Sims1: total number of nodes in search tree was "
<< detail::group_digits(stats.total_pending) << std::endl;
os << "#0: Sims1: max. number of pending definitions was "
<< detail::group_digits(stats.max_pending) << std::endl;
return os;
}
#endif
////////////////////////////////////////////////////////////////////////
// Sims1Settings
////////////////////////////////////////////////////////////////////////
template <typename T>
Sims1Settings<T>::Sims1Settings()
: _extra(),
_longs(),
_num_threads(),
_report_interval(),
_shorts()
#ifdef LIBSEMIGROUPS_ENABLE_STATS
,
_stats()
#endif
{
number_of_threads(1);
report_interval(999);
}
template <typename T>
template <typename S>
Sims1Settings<T>::Sims1Settings(Sims1Settings<S> const& that)
: _extra(that.extra()),
_longs(that.long_rules()),
_num_threads(that.number_of_threads()),
_report_interval(that.report_interval()),
_shorts(that.short_rules())
#ifdef LIBSEMIGROUPS_ENABLE_STATS
,
_stats(that.stats())
#endif
{
}
template <typename T>
template <typename P>
T& Sims1Settings<T>::short_rules(P const& p) {
static_assert(std::is_base_of<PresentationBase, P>::value,
"the template parameter P must be derived from "
"PresentationBase");
// This normalises the rules in the case they are of the right type but
// not normalised
if (p.alphabet().empty()) {
LIBSEMIGROUPS_EXCEPTION(
"the argument (Presentation) must not have 0 generators");
}
auto normal_p = make<Presentation<word_type>>(p);
validate_presentation(normal_p, long_rules());
validate_presentation(normal_p, extra());
_shorts = normal_p;
return static_cast<T&>(*this);
}
template <typename T>
template <typename P>
T& Sims1Settings<T>::long_rules(P const& p) {
static_assert(std::is_base_of<PresentationBase, P>::value,
"the template parameter P must be derived from "
"PresentationBase");
// We call make in the next two lines to ensure that the generators of
// the presentation are {0, ..., n - 1} where n is the size of the
// alphabet.
auto normal_p = make<Presentation<word_type>>(p);
validate_presentation(normal_p, short_rules());
validate_presentation(normal_p, extra());
_longs = normal_p;
return static_cast<T&>(*this);
}
template <typename T>
template <typename P>
T& Sims1Settings<T>::extra(P const& p) {
static_assert(std::is_base_of<PresentationBase, P>::value,
"the template parameter P must be derived from "
"PresentationBase");
auto normal_p = make<Presentation<word_type>>(p);
validate_presentation(normal_p, short_rules());
validate_presentation(normal_p, long_rules());
_extra = normal_p;
return static_cast<T&>(*this);
}
template <typename T>
T& Sims1Settings<T>::number_of_threads(size_t val) {
if (val == 0) {
LIBSEMIGROUPS_EXCEPTION("the argument (size_t) must be non-zero");
}
_num_threads = val;
return static_cast<T&>(*this);
}
template <typename T>
T& Sims1Settings<T>::long_rule_length(size_t val) {
auto partition = [&val](auto first, auto last) {
for (; first != last; first += 2) {
if (first->size() + (first + 1)->size() >= val) {
break;
}
}
if (first == last) {
return first;
}
for (auto lhs = first + 2; lhs < last; lhs += 2) {
auto rhs = lhs + 1;
if (lhs->size() + rhs->size() < val) {
std::iter_swap(lhs, first++);
std::iter_swap(rhs, first++);
}
}
return first;
};
// points at the lhs of the first rule of length at least val
auto its = partition(_shorts.rules.begin(), _shorts.rules.end());
_longs.rules.insert(_longs.rules.end(),
std::make_move_iterator(its),
std::make_move_iterator(_shorts.rules.end()));
auto lastl = _longs.rules.end() - std::distance(its, _shorts.rules.end());
_shorts.rules.erase(its, _shorts.rules.end());
// points at the lhs of the first rule of length at least val
auto itl = partition(_longs.rules.begin(), lastl);
_shorts.rules.insert(_shorts.rules.end(),
std::make_move_iterator(_longs.rules.begin()),
std::make_move_iterator(itl));
_longs.rules.erase(_longs.rules.begin(), itl);
return static_cast<T&>(*this);
}
template <typename T>
void Sims1Settings<T>::split_at(size_t val) {
if (val > _shorts.rules.size() / 2 + _longs.rules.size() / 2) {
LIBSEMIGROUPS_EXCEPTION(
"expected a value in the range [0, %llu), found %llu",
uint64_t(_shorts.rules.size() / 2 + _longs.rules.size() / 2),
uint64_t(val));
}
val *= 2;
if (val < _shorts.rules.size()) {
_longs.rules.insert(_longs.rules.begin(),
_shorts.rules.begin() + val,
_shorts.rules.end());
_shorts.rules.erase(_shorts.rules.begin() + val, _shorts.rules.end());
} else {
val -= _shorts.rules.size();
_shorts.rules.insert(_shorts.rules.end(),
_longs.rules.begin(),
_longs.rules.begin() + val);
_longs.rules.erase(_longs.rules.begin(), _longs.rules.begin() + val);
}
}
template <typename T>
void Sims1Settings<T>::validate_presentation(
Presentation<word_type> const& arg,
Presentation<word_type> const& existing) {
if (!arg.alphabet().empty() && !existing.alphabet().empty()
&& arg.alphabet() != existing.alphabet()) {
LIBSEMIGROUPS_EXCEPTION(
"the argument (a presentation) is not defined over "
"the correct alphabet, expected alphabet %s got %s",
detail::to_string(existing.alphabet()).c_str(),
detail::to_string(arg.alphabet()).c_str());
}
arg.validate();
}
////////////////////////////////////////////////////////////////////////
// Sims1
////////////////////////////////////////////////////////////////////////
template <typename T>
Sims1<T>::Sims1(congruence_kind ck) : Sims1Settings<Sims1<T>>(), _kind(ck) {
if (ck == congruence_kind::twosided) {
LIBSEMIGROUPS_EXCEPTION(
"expected congruence_kind::right or congruence_kind::left");
}
}
template <typename T>
Sims1<T>::~Sims1() = default;
template <typename T>
uint64_t Sims1<T>::number_of_congruences(size_type n) const {
if (number_of_threads() == 1) {
uint64_t result = 0;
for_each(n, [&result](digraph_type const&) { ++result; });
return result;
} else {
std::atomic_int64_t result(0);
for_each(n, [&result](digraph_type const&) { ++result; });
return result;
}
}
// Apply the function pred to every one-sided congruence with at
// most n classes
template <typename T>
void Sims1<T>::for_each(size_type n,
std::function<void(digraph_type const&)> pred) const {
if (n == 0) {
LIBSEMIGROUPS_EXCEPTION(
"expected the 1st argument (size_type) to be non-zero");
} else if (short_rules().rules.empty()
&& short_rules().alphabet().empty()) {
LIBSEMIGROUPS_EXCEPTION(
"the short_rules() must be defined before calling this function");
}
report_at_start(short_rules(), long_rules(), n, number_of_threads());
if (number_of_threads() == 1) {
if (!report::should_report()) {
// No stats in this case
std::for_each(cbegin(n), cend(n), pred);
} else {
auto start_time = std::chrono::high_resolution_clock::now();
auto last_report = start_time;
uint64_t last_count = 0;
uint64_t count = 0;
std::mutex mtx; // does nothing
auto it = cbegin(n);
auto const last = cend(n);
for (; it != last; ++it) {
report_number_of_congruences(report_interval(),
start_time,
last_report,
last_count,
++count,
mtx);
pred(*it);
}
final_report_number_of_congruences(start_time, count);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
// Copy the iterator stats into this so that we can retrieve it
// after den is destroyed.
stats(it.stats());
report_stats();
#endif
}
} else {
thread_runner den(short_rules(),
extra(),
long_rules(),
n,
number_of_threads(),
report_interval());
auto pred_wrapper = [&pred](digraph_type const& ad) {
pred(ad);
return false;
};
den.run(pred_wrapper);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
// Copy the thread_runner stats into this so that we can retrieve it
// after den is destroyed.
stats(den.stats());
report_stats();
#endif
}
}
template <typename T>
typename Sims1<T>::digraph_type
Sims1<T>::find_if(size_type n,
std::function<bool(digraph_type const&)> pred) const {
if (n == 0) {
LIBSEMIGROUPS_EXCEPTION(
"expected the 1st argument (size_type) to be non-zero");
} else if (short_rules().rules.empty()
&& short_rules().alphabet().empty()) {
LIBSEMIGROUPS_EXCEPTION(
"the short_rules() must be defined before calling this function");
}
report_at_start(short_rules(), long_rules(), n, number_of_threads());
if (number_of_threads() == 1) {
if (!report::should_report()) {
return *std::find_if(cbegin(n), cend(n), pred);
} else {
auto start_time = std::chrono::high_resolution_clock::now();
auto last_report = start_time;
uint64_t last_count = 0;
uint64_t count = 0;
std::mutex mtx; // does nothing
auto it = cbegin(n);
auto const last = cend(n);
for (; it != last; ++it) {
if (pred(*it)) {
final_report_number_of_congruences(start_time, ++count);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
stats(it.stats());
report_stats();
#endif
return *it;
}
report_number_of_congruences(report_interval(),
start_time,
last_report,
last_count,
++count,
mtx);
}
final_report_number_of_congruences(start_time, ++count);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
// Copy the iterator stats into this so that we can retrieve it
// after it is destroyed.
stats(it.stats());
report_stats();
#endif
return *last; // the empty digraph
}
} else {
thread_runner den(short_rules(),
extra(),
long_rules(),
n,
number_of_threads(),
report_interval());
den.run(pred);
#ifdef LIBSEMIGROUPS_ENABLE_STATS
// Copy the thread_runner stats into this so that we can retrieve it
// after den is destroyed.
stats(den.stats());
report_stats();
#endif
return den.digraph();
}
}
template <typename T>
void Sims1<T>::report_at_start(Presentation<word_type> const& shorts,
Presentation<word_type> const& longs,
size_t num_classes,
size_t num_threads) {
if (num_threads == 1) {
REPORT_DEFAULT_V3("Sims1: using 0 additional threads\n");
} else {
REPORT_DEFAULT_V3("Sims1: using %d / %d additional threads\n",
num_threads,
std::thread::hardware_concurrency());
}
REPORT_DEFAULT_V3("Sims1: finding congruences with at most %llu classes\n",
uint64_t(num_classes));
REPORT_DEFAULT_V3(
"Sims1: using %llu generators, and %llu short relations u = v"
" with:\n",
shorts.alphabet().size(),
shorts.rules.size() / 2);
uint64_t shortest_short, longest_short;
if (shorts.rules.empty()) {
shortest_short = 0;
longest_short = 0;
} else {
shortest_short = presentation::shortest_rule_length(shorts);
longest_short = presentation::longest_rule_length(shorts);
}
REPORT_DEFAULT_V3(
"Sims1: |u| + |v| \u2208 [%llu, %llu] and \u2211(|u| + |v|) = %llu\n",
shortest_short,
longest_short,
presentation::length(shorts));
if (!longs.rules.empty()) {
REPORT_DEFAULT_V3("Sims1: %llu long relations u = v with:\n",
longs.rules.size() / 2);
REPORT_DEFAULT_V3(
"Sims1: |u| + |v| \u2208 [%llu, %llu] and \u2211(|u| + |v|) = %llu\n",
presentation::shortest_rule_length(longs),
presentation::longest_rule_length(longs),
presentation::length(longs));
}
}
template <typename T>
template <typename S>
void Sims1<T>::report_number_of_congruences(uint64_t report_interval,
time_point& start_time,
time_point& last_report,
S& last_count,
uint64_t count_now,
std::mutex& mtx) {
using std::chrono::duration_cast;
using std::chrono::seconds;
std::lock_guard<std::mutex> lock(mtx);
if (count_now - last_count > report_interval) {
auto now = std::chrono::high_resolution_clock::now();
if (now - last_report > std::chrono::seconds(1)) {
auto total_time = duration_cast<seconds>(now - start_time);
auto diff_time = duration_cast<seconds>(now - last_report);
REPORT_DEFAULT_V3(
"Sims1: found %s congruences in %llus (%s/s)!\n",
detail::group_digits(count_now).c_str(),
total_time.count(),
detail::group_digits((count_now - last_count) / diff_time.count())
.c_str());
std::swap(now, last_report);
last_count = count_now;
}
}
}
template <typename T>
void Sims1<T>::final_report_number_of_congruences(time_point& start_time,
uint64_t count) {
using std::chrono::duration_cast;
using std::chrono::nanoseconds;
auto elapsed = duration_cast<nanoseconds>(
std::chrono::high_resolution_clock::now() - start_time);
if (count != 0) {
REPORT_DEFAULT_V3(
"Sims1: found %s congruences in %s (%s per congruence)!\n",
detail::group_digits(count).c_str(),
detail::Timer::string(elapsed).c_str(),
detail::Timer::string(elapsed / count).c_str());
} else {
REPORT_DEFAULT_V3("Sims1: found %s congruences in %s!\n",
detail::group_digits(count).c_str(),
detail::Timer::string(elapsed).c_str());
}
}
#ifdef LIBSEMIGROUPS_ENABLE_STATS
template <typename T>
void Sims1<T>::report_stats() const {
REPORT_DEFAULT("total number of nodes in search tree was %s\n",
detail::group_digits(stats().total_pending).c_str());
REPORT_DEFAULT("max. number of pending definitions was %s\n",
detail::group_digits(stats().max_pending).c_str());
}
#endif
///////////////////////////////////////////////////////////////////////////////
// iterator_base nested class
///////////////////////////////////////////////////////////////////////////////
template <typename T>
Sims1<T>::iterator_base::iterator_base(Presentation<word_type> const& p,
Presentation<word_type> const& extra,
Presentation<word_type> const& final_,
size_type n)
: // private
_extra(extra),
_longs(final_),
_max_num_classes(p.contains_empty_word() ? n : n + 1),
_min_target_node(p.contains_empty_word() ? 0 : 1),
// protected
_felsch_graph(p, n),
_mtx(),
_pending() {
// n == 0 only when the iterator is cend
_felsch_graph.number_of_active_nodes(n == 0 ? 0 : 1);
// = 0 indicates iterator is done
}
// The following function is separated from the constructor so that it isn't
// called in the constructor of every thread_iterator
template <typename T>
void Sims1<T>::iterator_base::init(size_type n) {
if (n != 0) {
if (n > 1 || _min_target_node == 1) {
_pending.emplace_back(0, 0, 1, 0, 2);
}
if (_min_target_node == 0) {
_pending.emplace_back(0, 0, 0, 0, 1);
}
}
}
template <typename T>
bool Sims1<T>::iterator_base::try_pop(PendingDef& pd) {
std::lock_guard<std::mutex> lock(_mtx);
if (_pending.empty()) {
return false;
}
pd = std::move(_pending.back());
_pending.pop_back();
return true;
}
template <typename T>
bool Sims1<T>::iterator_base::try_define(PendingDef const& current) {
LIBSEMIGROUPS_ASSERT(current.target < current.num_nodes);
LIBSEMIGROUPS_ASSERT(current.num_nodes <= _max_num_classes);
{
std::lock_guard<std::mutex> lock(_mtx);
// Backtrack if necessary
_felsch_graph.reduce_number_of_edges_to(current.num_edges);
// It might be that current.target is a new node, in which case
// _felsch_graph.number_of_active_nodes() includes this new node even
// before the edge current.source -> current.target is defined.
_felsch_graph.number_of_active_nodes(current.num_nodes);
LIBSEMIGROUPS_ASSERT(
_felsch_graph.unsafe_neighbor(current.source, current.generator)
== UNDEFINED);
size_type const start = _felsch_graph.number_of_edges();
_felsch_graph.def_edge(current.source, current.generator, current.target);
auto first = _extra.rules.cbegin();
auto last = _extra.rules.cend();
if (!felsch_digraph::compatible(_felsch_graph, 0, first, last)
|| !_felsch_graph.process_definitions(start)) {
// Seems to be important to check _extra first then
// process_definitions
return false;
}
}
letter_type a = current.generator + 1;
size_type const M = _felsch_graph.number_of_active_nodes();
size_type const N = _felsch_graph.number_of_edges();
size_type const num_gens = _felsch_graph.out_degree();
for (node_type next = current.source; next < M; ++next) {
for (; a < num_gens; ++a) {
if (_felsch_graph.unsafe_neighbor(next, a) == UNDEFINED) {
std::lock_guard<std::mutex> lock(_mtx);
if (M < _max_num_classes) {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
++_stats.total_pending;
#endif
_pending.emplace_back(next, a, M, N, M + 1);
}
for (node_type b = M; b-- > _min_target_node;) {
_pending.emplace_back(next, a, b, N, M);
}
#ifdef LIBSEMIGROUPS_ENABLE_STATS
_stats.total_pending += M - _min_target_node;
_stats.max_pending = std::max(static_cast<uint64_t>(_pending.size()),
_stats.max_pending);
#endif
return false;
}
}
a = 0;
}
// No undefined edges, word graph is complete
LIBSEMIGROUPS_ASSERT(N == M * num_gens);
auto first = _longs.rules.cbegin();
auto last = _longs.rules.cend();
return felsch_digraph::compatible(_felsch_graph, 0, M, first, last);
}
template <typename T>
Sims1<T>::iterator_base::iterator_base(Sims1<T>::iterator_base const& that)
: _extra(that._extra),
_longs(that._longs),
_max_num_classes(that._max_num_classes),
_min_target_node(that._min_target_node),
_felsch_graph(that._felsch_graph),
_pending(that._pending) {}
// Intentionally don't copy the mutex, it doesn't compile, wouldn't make
// sense if the mutex was used here.
template <typename T>
Sims1<T>::iterator_base::iterator_base(Sims1<T>::iterator_base&& that)
: _extra(std::move(that._extra)),
_longs(std::move(that._longs)),
_max_num_classes(std::move(that._max_num_classes)),
_min_target_node(std::move(that._min_target_node)),
_felsch_graph(std::move(that._felsch_graph)),
_pending(std::move(that._pending)) {}
// Intentionally don't copy the mutex, it doesn't compile, wouldn't make
// sense if the mutex was used here.
template <typename T>
typename Sims1<T>::iterator_base&
Sims1<T>::iterator_base::operator=(Sims1<T>::iterator_base const& that) {
_extra = that._extra;
_longs = that._longs;
_max_num_classes = that._max_num_classes;
_min_target_node = that._min_target_node;
_felsch_graph = that._felsch_graph;
_pending = that._pending;
return *this;
}
// Intentionally don't copy the mutex, it doesn't compile, wouldn't make
// sense if the mutex was used here.
template <typename T>
typename Sims1<T>::iterator_base&
Sims1<T>::iterator_base::operator=(Sims1<T>::iterator_base&& that) {
_extra = std::move(that._extra);
_longs = std::move(that._longs);
_max_num_classes = std::move(that._max_num_classes);
_min_target_node = std::move(that._min_target_node);
_felsch_graph = std::move(that._felsch_graph);
_pending = std::move(that._pending);
return *this;
}
template <typename T>
void Sims1<T>::iterator_base::swap(Sims1<T>::iterator_base& that) noexcept {
std::swap(_extra, that._extra);
std::swap(_felsch_graph, that._felsch_graph);
std::swap(_max_num_classes, that._max_num_classes);
std::swap(_min_target_node, that._min_target_node);
std::swap(_pending, that._pending);
}
///////////////////////////////////////////////////////////////////////////////
// iterator nested class
///////////////////////////////////////////////////////////////////////////////
template <typename T>
Sims1<T>::iterator::iterator(Presentation<word_type> const& p,
Presentation<word_type> const& e,
Presentation<word_type> const& f,
size_type n)
: iterator_base(p, e, f, n) {
if (this->_felsch_graph.number_of_active_nodes() == 0) {
return;
}
init(n);
++(*this);
// The increment above is required so that when dereferencing any
// instance of this type we obtain a valid word graph (o/w the value
// pointed to here is empty).
}
template <typename T>
typename Sims1<T>::iterator const& Sims1<T>::iterator::operator++() {
PendingDef current;
while (try_pop(current)) {
if (try_define(current)) {
return *this;
}
}
this->_felsch_graph.number_of_active_nodes(0);
// indicates that the iterator is done
this->_felsch_graph.restrict(0);
return *this;
}
///////////////////////////////////////////////////////////////////////////////
// thread_iterator
///////////////////////////////////////////////////////////////////////////////
// Note that this class is private, and not really an iterator in the usual
// sense. It is designed solely to work with thread_runner.
template <typename T>
class Sims1<T>::thread_iterator : public iterator_base {
friend class Sims1<T>::thread_runner;
using iterator_base::copy_felsch_graph;
public:
//! No doc
thread_iterator(Presentation<word_type> const& p,
Presentation<word_type> const& e,
Presentation<word_type> const& f,
size_type n)
: iterator_base(p, e, f, n) {}
// None of the constructors are noexcept because the corresponding
// constructors for std::vector aren't (until C++17).
//! No doc
thread_iterator() = delete;
//! No doc
thread_iterator(thread_iterator const&) = delete;
//! No doc
thread_iterator(thread_iterator&&) = delete;
//! No doc
thread_iterator& operator=(thread_iterator const&) = delete;
//! No doc
thread_iterator& operator=(thread_iterator&&) = delete;
//! No doc
~thread_iterator() = default;
#ifdef LIBSEMIGROUPS_ENABLE_STATS
using iterator_base::stats;
#endif
public:
void push(PendingDef pd) {
this->_pending.push_back(std::move(pd));
}
void steal_from(thread_iterator& that) {
// WARNING <that> must be locked before calling this function
std::lock_guard<std::mutex> lock(this->_mtx);
LIBSEMIGROUPS_ASSERT(this->_pending.empty());
size_t const n = that._pending.size();
if (n == 1) {
return;
}
// Copy the FelschDigraph from that into *this
copy_felsch_graph(that);
// Unzip that._pending into _pending and that._pending, this seems to
// give better performance in the search than splitting that._pending
// into [begin, begin + size / 2) and [begin + size / 2, end)
size_t i = 0;
for (; i < n - 2; i += 2) {
this->_pending.push_back(std::move(that._pending[i]));
that._pending[i / 2] = std::move(that._pending[i + 1]);
}
this->_pending.push_back(std::move(that._pending[i]));
if (i == n - 2) {
that._pending[i / 2] = std::move(that._pending[i + 1]);
}
that._pending.erase(that._pending.cbegin() + that._pending.size() / 2,
that._pending.cend());
}
bool try_steal(thread_iterator& q) {
std::lock_guard<std::mutex> lock(this->_mtx);
if (this->_pending.empty()) {
return false;
}
// Copy the FelschDigraph and half pending from *this into q
q.steal_from(*this); // Must call steal_from on q, so that q is locked
return true;
}
};
////////////////////////////////////////////////////////////////////////
// thread_runner
////////////////////////////////////////////////////////////////////////
template <typename T>
class Sims1<T>::thread_runner {
private:
std::atomic_bool _done;
std::vector<std::unique_ptr<thread_iterator>> _theives;
std::vector<std::thread> _threads;
std::mutex _mtx;
size_type _num_threads;
uint64_t _report_interval;
digraph_type _result;
#ifdef LIBSEMIGROUPS_ENABLE_STATS
Sims1Stats _stats;
#endif
void worker_thread(unsigned my_index,
std::function<bool(digraph_type const&)> hook) {
PendingDef pd;
for (auto i = 0; i < 16; ++i) {
while ((pop_from_local_queue(pd, my_index)
|| pop_from_other_thread_queue(pd, my_index))
&& !_done) {
if (_theives[my_index]->try_define(pd)) {
if (hook(**_theives[my_index])) {
// hook returns true to indicate that we should stop early
std::lock_guard<std::mutex> lock(_mtx);
if (!_done) {
_done = true;
_result = **_theives[my_index];
}
return;
}
}
}
std::this_thread::yield();
// It's possible to reach here before all of the work is done,
// because by coincidence there's nothing in the local queue and
// nothing in any other queue either, this sometimes leads to
// threads shutting down earlier than desirable. On the other hand,
// maybe this is a desirable.
}
#ifdef LIBSEMIGROUPS_VERBOSE
REPORT_DEFAULT(
"this thread created %s nodes\n",
detail::group_digits(_theives[my_index]->stats().total_pending)
.c_str());
#endif
#ifdef LIBSEMIGROUPS_ENABLE_STATS
std::lock_guard<std::mutex> lock(_mtx);
_stats += _theives[my_index]->stats();
#endif
}
bool pop_from_local_queue(PendingDef& pd, unsigned my_index) {
return _theives[my_index]->try_pop(pd);
}
bool pop_from_other_thread_queue(PendingDef& pd, unsigned my_index) {
for (size_t i = 0; i < _theives.size() - 1; ++i) {
unsigned const index = (my_index + i + 1) % _theives.size();
// Could always do something different here, like find
// the largest queue and steal from that? I tried this and it didn't
// seem to be faster.
if (_theives[index]->try_steal(*_theives[my_index])) {
return pop_from_local_queue(pd, my_index);
}
}
return false;
}
public:
thread_runner(Presentation<word_type> const& p,
Presentation<word_type> const& e,
Presentation<word_type> const& f,
size_type n,
size_type num_threads,
uint64_t report_interval)
: _done(false),
_theives(),
_threads(),
_mtx(),
_num_threads(num_threads),
_report_interval(report_interval),
_result()
#ifdef LIBSEMIGROUPS_ENABLE_STATS
,
_stats()
#endif
{
for (size_t i = 0; i < _num_threads; ++i) {
_theives.push_back(std::make_unique<thread_iterator>(p, e, f, n));
}
_theives.front()->init(n);
}
~thread_runner() = default;
digraph_type const& digraph() const {
return _result;
}
void run(std::function<bool(digraph_type const&)> hook) {
detail::Timer t;
auto start_time = std::chrono::high_resolution_clock::now();
auto last_report = start_time;
std::atomic_uint64_t last_count(0);
std::atomic_uint64_t count(0);
auto actual_hook = hook;
if (report::should_report()) {
actual_hook = [&](digraph_type const& ad) {
if (hook(ad)) {
return true;
}
report_number_of_congruences(_report_interval,
start_time,
last_report,
last_count,
++count,
_mtx);
return false;
};
}
try {
detail::JoinThreads joiner(_threads);
for (size_t i = 0; i < _num_threads; ++i) {
_threads.push_back(std::thread(
&thread_runner::worker_thread, this, i, std::ref(actual_hook)));
}
} catch (...) {
_done = true;
throw;
}
final_report_number_of_congruences(start_time, count);
}
#ifdef LIBSEMIGROUPS_ENABLE_STATS
Sims1Stats const& stats() {
return _stats;
}
#endif
};
////////////////////////////////////////////////////////////////////////
// RepOrc helper class
////////////////////////////////////////////////////////////////////////
template <typename T>
ActionDigraph<T> RepOrc::digraph() const {
using digraph_type = typename Sims1<T>::digraph_type;
using node_type = typename digraph_type::node_type;
REPORT_DEFAULT(
"searching for a faithful rep. o.r.c. on [%llu, %llu) points\n",
_min,
_max + 1);
if (_min > _max || _max == 0) {
REPORT_DEFAULT(
"no faithful rep. o.r.c. exists in [%llu, %llu) = \u2205\n",
_min,
_max + 1);
return ActionDigraph<T>(0, 0);
}
report::suppress("FroidurePin");
std::atomic_uint64_t count(0);
auto hook = [&](digraph_type const& x) {
++count;
if (x.number_of_active_nodes() >= _min) {
auto first = (short_rules().contains_empty_word() ? 0 : 1);
auto S = make<FroidurePin<Transf<0, node_type>>>(
x, first, x.number_of_active_nodes());
if (short_rules().contains_empty_word()) {
auto one = S.generator(0).identity();
if (!S.contains(one)) {
S.add_generator(one);
}
}
LIBSEMIGROUPS_ASSERT(S.size() <= _size);
if (S.size() == _size) {
return true;
}
}
return false;
};
auto result
= Sims1<T>(congruence_kind::right).settings(*this).find_if(_max, hook);
if (result.number_of_active_nodes() == 0) {
REPORT_DEFAULT("no faithful rep. o.r.c. on [%llu, %llu) points found\n",
_min,
_max + 1);
result.restrict(0);
} else {
REPORT_DEFAULT("found a faithful rep. o.r.c. on %llu points\n",
result.number_of_active_nodes());
if (short_rules().contains_empty_word()) {
result.restrict(result.number_of_active_nodes());
} else {
result.induced_subdigraph(1, result.number_of_active_nodes());
result.number_of_active_nodes(result.number_of_active_nodes() - 1);
}
}
report::clear_suppressions();
return result;
}
////////////////////////////////////////////////////////////////////////
// MinimalRepOrc helper class
////////////////////////////////////////////////////////////////////////
// An alternative to the approach used below would be to do a sort of
// binary search for a minimal representation. It seems that in most
// examples that I've tried, this actually finds the minimal rep close to
// the start of the search. The binary search approach would only really
// be useful if the rep found at the start of the search was much larger
// than the minimal one, and that the minimal one would not be found until
// late in the search through all the reps with [1, best rep so far). It
// seems that in most examples, binary search will involve checking many
// of the same graphs repeatedly, i.e. if we check [1, size = 1000) find a
// rep on 57 points, then check [1, 57 / 2) find nothing, then check
// [57/2, 57) and find nothing, then the search through [57/2, 57)
// actually iterates through all the digraphs with [1, 57 / 2) again (just
// doesn't construct a FroidurePin object for these). So, it seems to be
// best to just search through the digraphs with [1, 57) nodes once.
template <typename T>
ActionDigraph<T> MinimalRepOrc::digraph() const {
auto cr = RepOrc(*this);
size_t hi = (short_rules().contains_empty_word() ? _size : _size + 1);
auto best = cr.min_nodes(1).max_nodes(hi).target_size(_size).digraph();
if (best.number_of_nodes() < 1) {
#ifdef LIBSEMIGROUPS_ENABLE_STATS
stats(cr.stats());
#endif
return best;
}
hi = best.number_of_nodes();
ActionDigraph<T> next = cr.max_nodes(hi - 1).digraph();
while (next.number_of_nodes() != 0) {
hi = next.number_of_nodes();
best = std::move(next);
next = std::move(cr.max_nodes(hi - 1).digraph());
}
#ifdef LIBSEMIGROUPS_ENABLE_STATS
stats(cr.stats());
#endif
return best;
}
} // namespace libsemigroups
[ Dauer der Verarbeitung: 0.32 Sekunden
(vorverarbeitet)
]
|
2026-03-28
|