Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  sims1.tpp   Sprache: unbekannt

 
Spracherkennung für: .tpp vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs 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

[ Dauer der Verarbeitung: 0.51 Sekunden  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge