Spracherkennung für: .tpp vermutete Sprache: Masm {Masm[40] Python[33] C[30]} [Methode: maximale Elemente, drei Dimensionen]
//
// 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