//
// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2019-2026 Finn Smith
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// This file contains a generic implementation of Konieczny's algorithm,
// originally for computing subsemigroups of the boolean matrix monoid.

// TODO(later):
// * exception safety!
// * expose iterators to relevant things in D-classes, in particular elements

#ifndef LIBSEMIGROUPS_KONIECZNY_HPP_
#define LIBSEMIGROUPS_KONIECZNY_HPP_

#include <algorithm>  // for binary_search
#include <cstddef>    // for size_t
#include <initializer_list>
#include <set>            // for set
#include <type_traits>    // for is_pointer
#include <unordered_map>  // for unordered_map
#include <unordered_set>  // for unordered_set
#include <utility>        // for pair, make_pair
#include <vector>         // for vector

#include "action.hpp"     // for LeftAction, RightAction
#include "adapters.hpp"   // for Lambda, etc
#include "constants.hpp"  // for UNDEFINED
#include "debug.hpp"      // for LIBSEMIGROUPS_ASSERT
#include "exception.hpp"  // for LIBSEMIGROUPS_EXCEPTION
#include "runner.hpp"     // for Runner

#include "detail/pool.hpp"    // for detail::Pool
#include "detail/report.hpp"  // for report_default
#include "detail/timer.hpp"   // for Timer

#include "detail/bruidhinn-traits.hpp"  // for BruidhinnTraits

namespace libsemigroups {

  //! \defgroup konieczny_group Konieczny
  //!
  //! This page contains documentation related to the implementation of
  //! %Konieczny's algorithm \cite Konieczny1994aa in `libsemigroups`;
  //! this is similar to the algorithm of Lallement-McFadden
  //! \cite Lallement1990aa .
  //!
  //! The purpose of %Konieczny's algorithm is to determine the elements
  //! and structure of a semigroup or monoid generated by a set of generators
  //! from a finite over-monoid which acts on sets on the left and on the right
  //! in an appropriate way. The prototypical example, as described by
  //! %Konieczny, is the monoid of boolean matrices. Adapters for %Konieczny are
  //! included for BMat8, \ref BMat, Transf, and PPerm. These final three types
  //! currently have a maximum degree of 64 when used in Konieczny.
  //!
  //! \code
  //!  auto S = make<Konieczny>({
  //!     BMat8({{0, 1, 0, 0}, {1, 0, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}),
  //!     BMat8({{0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}, {1, 0, 0, 0}}),
  //!     BMat8({{1, 0, 0, 0}, {1, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}),
  //!     BMat8({{0, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}})});
  //!  S.size();   // returns 63'904
  //!  S.number_of_idempotents();   //returns 2'360
  //! \endcode

  //! \ingroup konieczny_group
  //!
  //! \brief This is a traits class for use with Konieczny.
  //!
  //! \tparam Element the type of the elements.
  //!
  //! \sa Konieczny
  template <typename Element>
  struct KoniecznyTraits {
    //! The type of the elements of a Konieczny instance with const removed.
    using element_type = typename detail::BruidhinnTraits<Element>::value_type;

    //! The type of const elements of a Konieczny instance.
    using const_element_type =
        typename detail::BruidhinnTraits<Element>::const_value_type;

    //! Alias for \ref LambdaValue with template parameter
    //! \ref element_type.
    using lambda_value_type =
        typename ::libsemigroups::LambdaValue<element_type>::type;

    //! Alias for RhoValue with template parameter \ref element_type.
    using rho_value_type =
        typename ::libsemigroups::RhoValue<element_type>::type;

    //! Alias for \ref RhoValue with template parameter \ref element_type.
    using rank_state_type = typename ::libsemigroups::RankState<element_type>;

    //! The type of the orbit of the lambda values under ImageRightAction.
    //! \sa ImageRightAction and RightAction
    using lambda_orb_type
        = RightAction<element_type,
                      lambda_value_type,
                      ImageRightAction<element_type, lambda_value_type>>;

    //! The type of the orbit of the rho values under ImageLeftAction
    //! \sa ImageLeftAction and LeftAction
    using rho_orb_type
        = LeftAction<element_type,
                     rho_value_type,
                     ImageLeftAction<element_type, rho_value_type>>;

    //! \copydoc libsemigroups::Lambda
    using Lambda = ::libsemigroups::Lambda<element_type, lambda_value_type>;

    //! \copydoc libsemigroups::Rho
    using Rho = ::libsemigroups::Rho<element_type, rho_value_type>;

    //! \copydoc libsemigroups::Product
    using Product = ::libsemigroups::Product<element_type>;

    //! \copydoc libsemigroups::Rank
    using Rank = ::libsemigroups::Rank<element_type, rank_state_type>;

    //! \copydoc libsemigroups::One
    using One = ::libsemigroups::One<element_type>;

    //! \copydoc libsemigroups::Hash
    using ElementHash = ::libsemigroups::Hash<element_type>;

    //! \copydoc libsemigroups::EqualTo
    using EqualTo = ::libsemigroups::EqualTo<element_type>;

    //! \copydoc libsemigroups::Swap
    using Swap = ::libsemigroups::Swap<element_type>;

    //! \copydoc libsemigroups::Less
    using Less = ::libsemigroups::Less<element_type>;

    //! \copydoc libsemigroups::Degree
    using Degree = ::libsemigroups::Degree<element_type>;
  };

  //! \ingroup konieczny_group
  //!
  //! \brief Class implementing Konieczny's algorithm.
  //!
  //! The class template Konieczny implements %Konieczny's algorithm as
  //! described in the article 'Green's equivalences in finite semigroups of
  //! binary relations' by Janusz %Konieczny \cite Konieczny1994aa.
  //! This algorithm is similar to that of Lallement and McFadden; see
  //! \cite Lallement1990aa. It differs in being applicable to subsemigroups of
  //! a non-regular semigroup, though is essentially the same algorithm for
  //! elements which happen to be regular.
  //!
  //! A Konieczny instance is defined by a generating set, and the main
  //! function is Konieczny::run, which implements
  //! %Konieczny's Algorithm. If Konieczny::run is invoked and
  //! Konieczny::finished returns \c true, then the size, partial order of
  //! \f$\mathscr{D}\f$-classes, and frames for each
  //! \f$\mathscr{D}\f$-class are known.
  //!
  //! \tparam Element the type of the elements of the semigroup (must not
  //! be a pointer).
  //!
  //! \tparam Traits the type of a traits class with the requirements of
  //! KoniecznyTraits.
  //!
  //! \sa KoniecznyTraits and DClass
  template <typename Element, typename Traits = KoniecznyTraits<Element>>
  class Konieczny : public Runner, private detail::BruidhinnTraits<Element> {
    // pointers are not currently supported
    static_assert(!std::is_pointer_v<Element>,
                  "Pointer types are not currently supported by Konieczny");

    ////////////////////////////////////////////////////////////////////////
    // Konieczny - aliases - private
    ////////////////////////////////////////////////////////////////////////

    using internal_element_type =
        typename detail::BruidhinnTraits<Element>::internal_value_type;
    using internal_const_element_type =
        typename detail::BruidhinnTraits<Element>::internal_const_value_type;
    using internal_const_reference =
        typename detail::BruidhinnTraits<Element>::internal_const_reference;
    using internal_reference =
        typename detail::BruidhinnTraits<Element>::internal_reference;

    using PairHash = Hash<std::pair<size_t, size_t>>;

   public:
    ////////////////////////////////////////////////////////////////////////
    // Konieczny - aliases - public
    ////////////////////////////////////////////////////////////////////////

    //! The type of elements.
    using element_type = typename Traits::element_type;

    //! The type of const elements.
    using const_element_type = typename Traits::const_element_type;

    //! Type of element const references.
    using const_reference =
        typename detail::BruidhinnTraits<Element>::const_reference;

    //! Type of indices of \f$\mathscr{D}\f$-classes.
    //!
    //! \sa cbegin_current_D_classes and cbegin_current_regular_D_classes.
    using D_class_index_type = size_t;

    //! The type of lambda values.
    using lambda_value_type = typename Traits::lambda_value_type;

    //! The type of the orbit of the lambda values.
    using lambda_orb_type = typename Traits::lambda_orb_type;

    //! The type of rho values.
    using rho_value_type = typename Traits::rho_value_type;

    //! The type of the orbit of the rho values.
    using rho_orb_type = typename Traits::rho_orb_type;

    //! \copydoc libsemigroups::Degree
    using Degree = typename Traits::Degree;

    //! \copydoc libsemigroups::EqualTo
    using EqualTo = typename Traits::EqualTo;

    //! \copydoc libsemigroups::Lambda
    using Lambda = typename Traits::Lambda;

    //! \copydoc libsemigroups::Less
    using Less = typename Traits::Less;

    //! \copydoc libsemigroups::One
    using One = typename Traits::One;

    //! \copydoc libsemigroups::Product
    using Product = typename Traits::Product;

    //! \copydoc libsemigroups::Rank
    using Rank = typename Traits::Rank;

    //! \copydoc libsemigroups::Rho
    using Rho = typename Traits::Rho;

    //! \copydoc libsemigroups::Swap
    using Swap = typename Traits::Swap;

   private:
    ////////////////////////////////////////////////////////////////////////
    // Konieczny - aliases - private
    ////////////////////////////////////////////////////////////////////////

    using lambda_orb_index_type    = typename lambda_orb_type::index_type;
    using rho_orb_index_type       = typename rho_orb_type::index_type;
    using rank_type                = size_t;
    using rank_state_type          = typename Traits::rank_state_type;
    using left_indices_index_type  = size_t;
    using right_indices_index_type = size_t;

    ////////////////////////////////////////////////////////////////////////
    // Konieczny - internal structs - private
    ////////////////////////////////////////////////////////////////////////

    struct InternalHash : private detail::BruidhinnTraits<Element> {
      size_t operator()(internal_const_element_type x) const {
        return Hash<Element>()(this->to_external_const(x));
      }
    };

    struct InternalEqualTo : private detail::BruidhinnTraits<Element> {
      bool operator()(internal_const_reference x,
                      internal_const_reference y) const {
        return EqualTo()(this->to_external_const(x),
                         this->to_external_const(y));
      }
    };

    struct InternalLess : private detail::BruidhinnTraits<Element> {
      bool operator()(internal_const_reference x,
                      internal_const_reference y) const {
        return Less()(this->to_external_const(x), this->to_external_const(y));
      }
    };

    struct InternalVecEqualTo : private detail::BruidhinnTraits<Element> {
      size_t operator()(std::vector<internal_element_type> const& x,
                        std::vector<internal_element_type> const& y) const {
        LIBSEMIGROUPS_ASSERT(x.size() == y.size());
        return std::equal(x.cbegin(), x.cend(), y.cbegin(), InternalEqualTo());
      }
    };

    struct InternalVecFree : private detail::BruidhinnTraits<Element> {
      void operator()(std::vector<internal_element_type> const& x) {
        for (auto it = x.cbegin(); it != x.cend(); ++it) {
          this->internal_free(*it);
        }
      }
    };

    struct InternalVecCopy : private detail::BruidhinnTraits<Element> {
      void operator()(std::vector<internal_element_type> const& source,
                      std::vector<internal_element_type>&       target) {
        InternalVecFree()(target);
        for (auto it = source.cbegin(); it != source.cend(); ++it) {
          target.push_back(this->internal_copy(*it));
        }
      }
    };

    struct OneParamLambda {
      lambda_value_type operator()(const_reference x) const {
        lambda_value_type lval;
        Lambda()(lval, x);
        return lval;
      }
    };

    struct OneParamRho {
      rho_value_type operator()(const_reference x) const {
        rho_value_type rval;
        Rho()(rval, x);
        return rval;
      }
    };

    struct InternalRank {
      template <typename SFINAE = size_t>
      auto operator()(void*, const_reference x)
          -> std::enable_if_t<std::is_void_v<typename rank_state_type::type>,
                              SFINAE> {
        return Rank()(x);
      }

      template <typename SFINAE = size_t>
      auto operator()(rank_state_type* state, const_reference x)
          -> std::enable_if_t<!std::is_void_v<typename rank_state_type::type>,
                              SFINAE> {
        return Rank()(*state, x);
      }
    };

    struct RepInfo {
      RepInfo(D_class_index_type    D_idx,
              internal_element_type elt,
              lambda_orb_index_type lambda_idx,
              rho_orb_index_type    rho_idx)
          : _D_idx(D_idx),
            _elt(elt),
            _lambda_idx(lambda_idx),
            _rho_idx(rho_idx) {}

      D_class_index_type    _D_idx;
      internal_element_type _elt;
      lambda_orb_index_type _lambda_idx;
      rho_orb_index_type    _rho_idx;
    };

   public:
    ////////////////////////////////////////////////////////////////////////
    // Konieczny - constructor and destructor - public
    ////////////////////////////////////////////////////////////////////////

    //! \brief Default constructor.
    //!
    //! This is the standard constructor for a Konieczny instance with
    //! unspecified generators.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \sa add_generator and add_generators
    Konieczny();

    //! \brief Reinitialize an existing Konieczny object.
    //!
    //! This function re-initializes a Konieczny instance so that it is in the
    //! same (logical) state as if it had just been default-constructed.
    //!
    //! \returns A reference to `*this`.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    Konieczny& init();

    //! \brief Copy constructor.
    //!
    //! Constructs a new Konieczny which is an exact copy of \p that. No
    //! enumeration is triggered for either \p that or of the newly constructed
    //! Konieczny object.
    //!
    //! \param that the Konieczny to copy.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    Konieczny(Konieczny const& that);

    //! \brief Move constructor.
    //!
    //! Constructs a new Konieczny using the data from \p that. No
    //! enumeration is triggered for either \p that or of the newly constructed
    //! Konieczny object. The object \p that is left in a state which is
    //! undefined, though guaranteed to be independent from the new Konieczny
    //! object.
    //!
    //! \param that the Konieczny to move from.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    Konieczny(Konieczny&& that);

    //! \brief Copy assignment operator.
    Konieczny& operator=(Konieczny const&);

    //! \brief Move assignment operator.
    Konieczny& operator=(Konieczny&&);

    ~Konieczny();

    ////////////////////////////////////////////////////////////////////////
    // Konieczny - forward class declarations - public/private
    ////////////////////////////////////////////////////////////////////////

    //! \ingroup konieczny_group
    //!
    //! \brief A class representing a \f$\mathscr{D}\f$-class in a Konieczny
    //! object.
    //!
    //! The nested abstract class Konieczny::DClass represents a
    //! \f$\mathscr{D}\f$-class via a frame as described in
    //! \cite Konieczny1994aa.
    //!
    //! As an abstract class, DClass cannot be directly constructed; instead you
    //! should obtain a \f$\mathscr{D}\f$-class by calling
    //! Konieczny::D_class_of_element, or through a function like
    //! Konieczny::cbegin_current_D_classes.
    //!
    //! \sa Konieczny.
    class DClass : protected detail::BruidhinnTraits<Element> {
      // This friend is only here so that the virtual contains(x, lpos, rpos)
      // method and the cbegin_left_indices etc. methods can be private.
      friend class Konieczny<Element, Traits>;

      ////////////////////////////////////////////////////////////////////////
      // DClass - aliases - protected
      ////////////////////////////////////////////////////////////////////////
#ifndef LIBSEMIGROUPS_PARSED_BY_DOXYGEN
      using konieczny_type    = Konieczny<Element, Traits>;
      using internal_set_type = std::
          unordered_set<internal_element_type, InternalHash, InternalEqualTo>;

      ////////////////////////////////////////////////////////////////////////
      // DClass - default constructor - private
      ////////////////////////////////////////////////////////////////////////

     private:
      DClass();

      void clear();

      ////////////////////////////////////////////////////////////////////////
      // DClass - constructors - protected
      ////////////////////////////////////////////////////////////////////////
     protected:
      DClass(Konieczny* parent, internal_reference rep);

      DClass(DClass const& that);

      DClass& operator=(DClass const& that);

      DClass(DClass&&)            = delete;
      DClass& operator=(DClass&&) = delete;

#endif  // ndef LIBSEMIGROUPS_PARSED_BY_DOXYGEN

     public:
      ////////////////////////////////////////////////////////////////////////
      // DClass - destructor - public
      ////////////////////////////////////////////////////////////////////////
      virtual ~DClass();
      ////////////////////////////////////////////////////////////////////////
      // DClass - member functions - public
      ////////////////////////////////////////////////////////////////////////

      //! \brief Get the representative of the \f$\mathscr{D}\f$-class.
      //!
      //! The frame used to represent \f$\mathscr{D}\f$-classes depends on the
      //! choice of representative. This function returns the representative
      //! used by a DClass instance. This may not be the same representative
      //! as used to construct the instance, but is guaranteed to not change.
      //!
      //! \returns
      //! A \ref const_reference.
      //!
      //! \exceptions
      //! \no_libsemigroups_except
      const_reference rep() const {
        return this->to_external_const(_rep);
      }

      //! \brief Get the size of a \f$\mathscr{D}\f$-class.
      //!
      //! This function triggers the computation of most of the frame
      //! for \c this, if it is not already known.
      //!
      //! \returns
      //! A value of type \c size_t.
      //!
      //! \exceptions
      //! \no_libsemigroups_except
      size_t size() const {
        LIBSEMIGROUPS_ASSERT(this->class_computed());
        return number_of_L_classes() * number_of_R_classes() * size_H_class();
      }

      //! \brief Get the number of \f$\mathscr{L}\f$-classes within a DClass.
      //!
      //! This function triggers the computation of most of the frame
      //! for \c this, if it is not already known.
      //!
      //! \returns
      //! A value of type \c size_t.
      //!
      //! \exceptions
      //! \no_libsemigroups_except
      size_t number_of_L_classes() const {
        LIBSEMIGROUPS_ASSERT(_left_mults.size() > 0);
        LIBSEMIGROUPS_ASSERT(this->class_computed());
        return _left_mults.size();
      }

      //! \brief Get the number of \f$\mathscr{R}\f$-classes within a DClass.
      //!
      //! This function triggers the computation of most of the frame
      //! for \c this, if it is not already known.
      //!
      //! \returns
      //! A value of type \c size_t.
      //!
      //! \exceptions
      //! \no_libsemigroups_except
      size_t number_of_R_classes() const {
        // compute_right_mults();
        LIBSEMIGROUPS_ASSERT(_right_mults.size() > 0);
        LIBSEMIGROUPS_ASSERT(this->class_computed());
        return _right_mults.size();
      }

      //! \brief Get the size of the \f$\mathscr{H}\f$-classes within a
      //! DClass.
      //!
      //! This function triggers the computation of most of the frame
      //! for \c this, if it is not already known.
      //!
      //! \returns
      //! A value of type \c size_t.
      //!
      //! \exceptions
      //! \no_libsemigroups_except
      size_t size_H_class() const {
        // compute_H_class();
        LIBSEMIGROUPS_ASSERT(_H_class.size() > 0);
        LIBSEMIGROUPS_ASSERT(this->class_computed());
        return _H_class.size();
      }

      //! \brief Test regularity of a \f$\mathscr{D}\f$-class.
      //!
      //! \returns
      //! A value of type \c size_t.
      //!
      //! \exceptions
      //! \noexcept
      bool is_regular_D_class() const noexcept {
        return _is_regular_D_class;
      }

      //! \brief Test membership of an element within a
      //! \f$\mathscr{D}\f$-class.
      //!
      //! Given an element \p x which may or may not belong to \c parent, this
      //! function returns whether \p x is an element of the
      //! \f$\mathscr{D}\f$-class represented by \c this.
      //! This function triggers the computation of most of the frame
      //! for \c this, if it is not already known.
      //!
      //! \param x the element.
      //!
      //! \returns
      //! A value of type \c bool.
      //!
      //! \exceptions
      //! \no_libsemigroups_except
      bool contains(const_reference x);

      //! \brief Get the number of idempotents of a \f$\mathscr{D}\f$-class.
      //!
      //! This function triggers the computation of most of the frame
      //! for \c this, if it is not already known.
      virtual size_t number_of_idempotents() const {
        return 0;
      }

     protected:
#ifndef LIBSEMIGROUPS_PARSED_BY_DOXYGEN
      ////////////////////////////////////////////////////////////////////////
      // DClass - iterators - protected
      ////////////////////////////////////////////////////////////////////////
      using const_iterator =
          typename std::vector<internal_element_type>::const_iterator;

      const_iterator cbegin_left_reps() {
        compute_left_reps();
        return _left_reps.cbegin();
      }

      const_iterator cend_left_reps() {
        compute_left_reps();
        return _left_reps.cend();
      }

      const_iterator cbegin_right_reps() {
        compute_right_reps();
        return _right_reps.cbegin();
      }

      const_iterator cend_right_reps() {
        compute_right_reps();
        return _right_reps.cend();
      }

      const_iterator cbegin_left_mults() {
        compute_left_mults();
        return _left_mults.cbegin();
      }

      const_iterator cend_left_mults() {
        compute_left_mults();
        return _left_mults.cend();
      }

      const_iterator cbegin_right_mults() {
        compute_right_mults();
        return _right_mults.cbegin();
      }

      const_iterator cend_right_mults() {
        compute_right_mults();
        return _right_mults.cend();
      }

      const_iterator cbegin_H_class() {
        compute_H_class();
        return _H_class.cbegin();
      }

      const_iterator cend_H_class() {
        compute_H_class();
        return _H_class.cend();
      }

      internal_element_type left_mults_inv(size_t i) {
        compute_left_mults_inv();
        return _left_mults_inv[i];
      }

      internal_element_type right_mults_inv(size_t i) {
        compute_right_mults_inv();
        return _right_mults_inv[i];
      }

      internal_element_type H_class_no_checks(size_t i) const {
        return _H_class[i];
      }

      ////////////////////////////////////////////////////////////////////////
      // DClass - initialisation member functions - protected
      ////////////////////////////////////////////////////////////////////////
      virtual void compute_frame()           = 0;
      virtual void compute_left_indices()    = 0;
      virtual void compute_left_mults()      = 0;
      virtual void compute_left_mults_inv()  = 0;
      virtual void compute_left_reps()       = 0;
      virtual void compute_right_indices()   = 0;
      virtual void compute_right_mults()     = 0;
      virtual void compute_right_mults_inv() = 0;
      virtual void compute_right_reps()      = 0;
      virtual void compute_H_class()         = 0;

      ////////////////////////////////////////////////////////////////////////
      // DClass - containment - protected
      ////////////////////////////////////////////////////////////////////////

      // Returns whether the element \p x belongs to this
      // \f$\mathscr{D}\f$-class.
      //
      // Given an element \p x of the semigroup represented by \c parent, this
      // function returns whether \p x is an element of the
      // \f$\mathscr{D}\f$-class represented by \c this. If \p x is not an
      // element of the semigroup, then the behaviour is undefined.
      // This member function involved computing most of the frame for
      // \c this, if it is not already known.
      bool contains_no_checks(internal_const_reference x);

      // Returns whether the element \p x belongs to this
      // \f$\mathscr{D}\f$-class.
      //
      // This overload of DClass::contains_no_checks is provided in order to
      // avoid recalculating the rank of \p x when it is already known.
      bool contains_no_checks(internal_const_reference x, size_t rank);

      // Returns whether the element \p x belongs to this
      // \f$\mathscr{D}\f$-class.
      //
      // This overload of DClass::contains_no_checks is provided in order to
      // avoid recalculating the rank, lambda value, and rho value of \p x
      // when they are already known.
      bool contains_no_checks(internal_const_reference x,
                              size_t                   rank,
                              lambda_orb_index_type    lpos,
                              rho_orb_index_type       rpos);

      // Returns whether the element \p x belongs to this
      // \f$\mathscr{D}\f$-class.
      //
      // This overload of DClass::contains_no_checks is provided in order to
      // avoid recalculating the lambda value and rho value of \p x  when they
      // are already known.
      virtual bool contains_no_checks(internal_const_reference x,
                                      lambda_orb_index_type    lpos,
                                      rho_orb_index_type       rpos)
          = 0;

      virtual bool contains(const_reference       x,
                            lambda_orb_index_type lpos,
                            rho_orb_index_type    rpos)
          = 0;

      ////////////////////////////////////////////////////////////////////////
      // DClass - accessor member functions - protected
      ////////////////////////////////////////////////////////////////////////

      size_t number_of_left_reps_no_checks() const noexcept {
        return _left_reps.size();
      }

      size_t number_of_right_reps_no_checks() const noexcept {
        return _right_reps.size();
      }

      size_t size_H_class_no_checks() const noexcept {
        return _H_class.size();
      }

      void push_left_mult(internal_const_reference x);

      void push_left_mult_inv(internal_const_reference x);

      void push_right_mult(internal_const_reference x);

      void push_right_mult_inv(internal_const_reference x);

      void push_left_rep(internal_const_reference x);

      void push_right_rep(internal_const_reference x);

      bool class_computed() const noexcept {
        return _class_computed;
      }

      bool mults_computed() const noexcept {
        return _mults_computed;
      }

      bool reps_computed() const noexcept {
        return _reps_computed;
      }

      bool H_class_computed() const noexcept {
        return _H_class_computed;
      }

      void set_class_computed(bool x) noexcept {
        _class_computed = x;
      }

      void set_mults_computed(bool x) noexcept {
        _mults_computed = x;
      }

      void set_reps_computed(bool x) noexcept {
        _reps_computed = x;
      }

      void set_H_class_computed(bool x) noexcept {
        _H_class_computed = x;
      }

      Konieczny* parent() const noexcept {
        return _parent;
      }

      void set_parent(Konieczny* x) noexcept {
        _parent = x;
      }

      // Watch out! Doesn't copy its argument
      void push_back_H_class(internal_element_type x) {
        _H_class.push_back(x);
      }

      std::vector<internal_element_type>& H_class() {
        return _H_class;
      }

      lambda_value_type& tmp_lambda_value() const noexcept {
        return _tmp_lambda_value;
      }

      rho_value_type& tmp_rho_value() const noexcept {
        return _tmp_rho_value;
      }

      rank_type rank() const noexcept {
        return _rank;
      }

      internal_set_type& internal_set() const noexcept {
        return _tmp_internal_set;
      }

      // Elements must be freed before next used
      std::vector<internal_element_type>& internal_vec() const noexcept {
        return _tmp_internal_vec;
      }

      internal_reference unsafe_rep() noexcept {
        return _rep;
      }

      std::vector<lambda_orb_index_type>& left_indices() {
        return _left_indices;
      }

      std::vector<rho_orb_index_type>& right_indices() {
        return _right_indices;
      }

     protected:
      ////////////////////////////////////////////////////////////////////////
      // DClass - index iterators - protected
      ////////////////////////////////////////////////////////////////////////

      typename std::vector<left_indices_index_type>::const_iterator
      cbegin_left_indices() {
        compute_left_indices();
        return _left_indices.cbegin();
      }

      typename std::vector<left_indices_index_type>::const_iterator
      cend_left_indices() {
        compute_left_indices();
        return _left_indices.cend();
      }

      typename std::vector<right_indices_index_type>::const_iterator
      cbegin_right_indices() {
        compute_right_indices();
        return _right_indices.cbegin();
      }

      typename std::vector<right_indices_index_type>::const_iterator
      cend_right_indices() {
        compute_right_indices();
        return _right_indices.cend();
      }
#endif

     private:
      ////////////////////////////////////////////////////////////////////////
      // DClass - member functions - private
      ////////////////////////////////////////////////////////////////////////

      // Returns a set of representatives of L- or R-classes covered by \c
      // this.
      //
      // The \f$\mathscr{D}\f$-classes of the parent semigroup are enumerated
      // either by finding representatives of all L-classes or all R-classes.
      // This member function returns the representatives obtainable by
      // multipliying the representatives  by generators on either the left or
      // right.
      std::vector<RepInfo>& covering_reps();

      ////////////////////////////////////////////////////////////////////////
      // DClass - data - private
      ////////////////////////////////////////////////////////////////////////
      bool                               _class_computed;
      std::vector<internal_element_type> _H_class;
      bool                               _H_class_computed;
      bool                               _is_regular_D_class;
      std::vector<lambda_orb_index_type> _left_indices;
      std::vector<internal_element_type> _left_mults;
      std::vector<internal_element_type> _left_mults_inv;
      std::vector<internal_element_type> _left_reps;
      bool                               _mults_computed;
      Konieczny*                         _parent;
      rank_type                          _rank;
      internal_element_type              _rep;
      bool                               _reps_computed;
      std::vector<rho_orb_index_type>    _right_indices;
      std::vector<internal_element_type> _right_mults;
      std::vector<internal_element_type> _right_mults_inv;
      std::vector<internal_element_type> _right_reps;
      mutable internal_set_type _tmp_internal_set;  // Does not own its elements
      mutable std::vector<RepInfo>
          _tmp_rep_info_vec;  // RepInfos do not own their rep
      mutable std::vector<internal_element_type> _tmp_internal_vec;  // Does not
      mutable lambda_value_type                  _tmp_lambda_value;
      mutable rho_value_type                     _tmp_rho_value;
    };

   private:
    class RegularDClass;
    class NonRegularDClass;

   public:
    ////////////////////////////////////////////////////////////////////////
    // Konieczny - member functions - public
    ////////////////////////////////////////////////////////////////////////

    //! \brief Returns the number of generators.
    //!
    //! This function returns the number of generators given to \c this.
    //! Note that there may be duplicate generators, and so \c this may have
    //! more generators than unique generators.
    //!
    //! \returns A value of type \c size_t.
    //!
    //! \exceptions
    //! \noexcept
    //!
    //! \sa add_generator and add_generators
    size_t number_of_generators() const noexcept {
      return _gens.empty() ? 0 : _gens.size() - 1;
    }

    //! \brief Returns a const reference to the generator given by an index.
    //!
    //! This function returns a const reference to the \p pos generators of
    //! \c this.
    //!
    //! \param pos the index of the generator.
    //!
    //! \returns
    //! A value of type \ref const_reference
    //!
    //! \throws LibsemigroupsException if the value of \p pos is greater than
    //! number_of_generators().
    //!
    //! \complexity
    //! Constant.
    //!
    //! \note There may be duplicate generators, and so \c this may
    //! have more generators than unique generators.
    //!
    //! \sa add_generator and add_generators
    const_reference generator(size_t pos) const {
      if (pos >= number_of_generators()) {
        LIBSEMIGROUPS_EXCEPTION(
            "index out of bounds, expected value in [{}, {}) found {}",
            0,
            number_of_generators(),
            pos);
      }
      return this->to_external_const(_gens[pos]);
    }

    //! \brief Returns the number of \f$\mathscr{D}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers a full enumeration of the frames of every
    //! \f$\mathscr{D}\f$-class.
    size_t number_of_D_classes() {
      run();
      return std::distance(cbegin_current_D_classes(),
                           cend_current_D_classes());
    }

    //! \brief Returns the current number of \f$\mathscr{D}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers no enumeration.
    size_t current_number_of_D_classes() const {
      return std::distance(cbegin_current_D_classes(),
                           cend_current_D_classes());
    }

    //! \brief Returns the number of regular \f$\mathscr{D}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers a full enumeration of the frames of every
    //! \f$\mathscr{D}\f$-class.
    size_t number_of_regular_D_classes() {
      run();
      return current_number_of_regular_D_classes();
    }

    //! \brief Returns the current number of regular \f$\mathscr{D}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers no enumeration.
    size_t current_number_of_regular_D_classes() const {
      return std::distance(cbegin_current_regular_D_classes(),
                           cend_current_regular_D_classes());
    }

    //! \brief Returns the number of \f$\mathscr{L}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers a full enumeration of the frames of every
    //! \f$\mathscr{D}\f$-class.
    size_t number_of_L_classes() {
      run();
      return current_number_of_L_classes();
    }

    //! \brief Returns the current number of \f$\mathscr{L}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers no enumeration.
    size_t current_number_of_L_classes() const {
      size_t val = 0;
      std::for_each(
          cbegin_current_D_classes(),
          cend_current_D_classes(),
          [&val](DClass const& D) { val += D.number_of_L_classes(); });
      return val;
    }

    //! \brief Returns the number of regular \f$\mathscr{L}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers a full enumeration of the frames of every
    //! \f$\mathscr{D}\f$-class.
    size_t number_of_regular_L_classes() {
      run();
      return current_number_of_regular_L_classes();
    }

    //! \brief Returns the current number of regular \f$\mathscr{L}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers no enumeration.
    size_t current_number_of_regular_L_classes() const {
      size_t val = 0;
      std::for_each(
          cbegin_current_regular_D_classes(),
          cend_current_regular_D_classes(),
          [&val](DClass const& D) { val += D.number_of_L_classes(); });
      return val;
    }

    //! \brief Returns the number of \f$\mathscr{R}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers a full enumeration of the frames of every
    //! \f$\mathscr{D}\f$-class.
    size_t number_of_R_classes() {
      run();
      return current_number_of_R_classes();
    }

    //! \brief Returns the current number of regular \f$\mathscr{R}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers no enumeration.
    size_t current_number_of_R_classes() const {
      size_t val = 0;
      std::for_each(
          cbegin_current_D_classes(),
          cend_current_D_classes(),
          [&val](DClass const& D) { val += D.number_of_R_classes(); });
      return val;
    }

    //! \brief Returns the number of regular \f$\mathscr{R}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers a full enumeration of the frames of every
    //! \f$\mathscr{D}\f$-class.
    size_t number_of_regular_R_classes() {
      run();
      return current_number_of_regular_R_classes();
    }

    //! \brief Returns the current number of regular \f$\mathscr{R}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers no enumeration.
    size_t current_number_of_regular_R_classes() const {
      size_t val = 0;
      std::for_each(
          cbegin_current_regular_D_classes(),
          cend_current_regular_D_classes(),
          [&val](DClass const& D) { val += D.number_of_R_classes(); });
      return val;
    }

    //! \brief Returns the number of \f$\mathscr{H}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers a full enumeration of the frames of every
    //! \f$\mathscr{D}\f$-class.
    size_t number_of_H_classes() {
      run();
      return current_number_of_H_classes();
    }

    //! \brief Returns the current number of \f$\mathscr{H}\f$-classes.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers no enumeration.
    size_t current_number_of_H_classes() const {
      size_t val = 0;
      std::for_each(cbegin_current_D_classes(),
                    cend_current_D_classes(),
                    [&val](DClass const& D) {
                      val += (D.number_of_R_classes()
                              * D.number_of_L_classes());
                    });
      return val;
    }

    //! \brief Returns the number of idempotents.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers a full enumeration of the frames of every
    //! \f$\mathscr{D}\f$-class.
    size_t number_of_idempotents() {
      run();
      return current_number_of_idempotents();
    }

    //! \brief Returns the current number of idempotents.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers no enumeration.
    size_t current_number_of_idempotents() const {
      size_t val = 0;
      std::for_each(
          cbegin_current_regular_D_classes(),
          cend_current_regular_D_classes(),
          [&val](RegularDClass const& D) { val += D.number_of_idempotents(); });
      return val;
    }

    //! \brief Returns the number of regular elements.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers a full enumeration of the frames of every
    //! \f$\mathscr{D}\f$-class.
    size_t number_of_regular_elements() {
      run();
      return current_number_of_regular_elements();
    }

    //! \brief Returns the current number of regular elements.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers no enumeration.
    size_t current_number_of_regular_elements() const {
      size_t val = 0;
      std::for_each(cbegin_current_regular_D_classes(),
                    cend_current_regular_D_classes(),
                    [&val](DClass const& D) { val += D.size(); });
      return val;
    }

    //! \brief Returns the size.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers a full enumeration of the frames of every
    //! \f$\mathscr{D}\f$-class.
    //!
    //! \sa current_size
    size_t size() {
      run();
      return current_size();
    }

    //! \brief Returns the current size.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function triggers no enumeration.
    //!
    //! \sa \ref size.
    size_t current_size() const {
      size_t val = 0;
      std::for_each(cbegin_current_D_classes(),
                    cend_current_D_classes(),
                    [&val](DClass const& D) { val += D.size(); });
      return val;
    }

    //! \brief Returns the degree of elements.
    //!
    //! All elements of a Konieczny must have the same degree, as computed by
    //! an instance of Degree; this function returns that degree.
    //!
    //! \returns
    //! A value of type \c size_t.
    //!
    //! \exceptions
    //! \noexcept
    //!
    //! \sa Degree
    size_t degree() const noexcept {
      return _degree;
    }

    //! \brief  Test membership of an element.
    //!
    //! Returns \c true if \p x belongs to \c this and \c false if it does
    //! not.
    //!
    //! \param x a const reference to a possible element.
    //!
    //! \returns A value of type \c bool.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note This function may trigger a (partial) enumeration.
    bool contains(const_reference x) {
      return Degree()(x) == degree()
             && get_containing_D_class(this->to_internal_const(x), true)
                    != UNDEFINED;
    }

    //! \brief  Returns the \f$\mathscr{D}\f$-class containing an element.
    //!
    //! \param x a const reference to a possible element.
    //!
    //! \returns A reference to DClass.
    //!
    //! \throws LibsemigroupsException if \p x does not belong to \c this.
    //!
    //! \note This involves computing as many frames for
    //! \f$\mathscr{D}\f$-classes  as necessary.
    DClass& D_class_of_element(const_reference x) {
      D_class_index_type i
          = get_containing_D_class(this->to_internal_const(x), true);
      if (i == UNDEFINED) {
        LIBSEMIGROUPS_EXCEPTION(
            "the argument does not belong to this semigroup!");
      }
      return *_D_classes[i];
    }

    //! \brief  Test regularity of an element.
    //!
    //! Returns \c true if \p x is a regular element and \c false if it is
    //! not.
    //!
    //! \param x a const reference to a possible element.
    //!
    //! \returns A value of type \c bool.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    //! \note
    //! This function triggers the computation of the orbits of the Lambda and
    //! Rho values, if they are not already known.
    bool is_regular_element(const_reference x) {
      return contains(x)
             && is_regular_element_no_checks(this->to_internal_const(x));
    }

    //! \brief Add collection of generators from iterators.
    //!
    //! Add copies of the generators in the range \p first to \p last to
    //! \c this.  See \ref add_generator for a detailed description.
    //!
    //! \tparam the type of an iterator pointing to an \ref element_type.
    //!
    //! \param first iterator pointing to the first generator to add.
    //! \param last iterator pointing one past the last generator to add.
    //!
    //! \returns A reference to `*this`.
    //!
    //! \throws LibsemigroupsException if any of the following hold:
    //! * the degree of \p x is incompatible with the existing degree.
    //! * \ref started returns \c true
    template <typename T>
    Konieczny& add_generators(T const& first, T const& last);

    //! \brief Add a copy of an element to the generators of a Konieczny.
    //!
    //! It is possible, if perhaps not desirable, to add the same generator
    //! multiple times.
    //!
    //! \param gen the generator to add.
    //!
    //! \returns A reference to `*this`.
    //!
    //! \throws LibsemigroupsException if any of the following hold:
    //! * the degree of \p x is incompatible with \c K.degree();
    //! * \c K.started() returns \c true.
    Konieczny& add_generator(const_reference gen) {
      return add_generators(&gen, &gen + 1);
    }

    ////////////////////////////////////////////////////////////////////////
    // Konieczny - iterators - public
    ////////////////////////////////////////////////////////////////////////

    //! A type for const iterators through elements.
    using const_iterator
        = detail::BruidhinnConstIterator<element_type,
                                         std::vector<internal_element_type>>;

    //! \brief  Returns a const iterator pointing to the first generator.
    //!
    //! This  function does not trigger any enumeration; the iterator returned
    //! may be invalidated by any call to a non-const member function of the
    //! Konieczny class.
    //!
    //! \returns
    //! A value of type \c const_iterator.
    //!
    //! \exceptions
    //! \noexcept
    //!
    //! \sa cend_generators
    const_iterator cbegin_generators() const noexcept {
      return const_iterator(_gens.cbegin());
    }

    //! \brief  Returns a const iterator pointing to one past the last
    //! generator.
    //!
    //! This  function does not trigger any enumeration; the iterator returned
    //! may be invalidated by any call to a non-const member function of the
    //! Konieczny class.
    //!
    //! \returns
    //! A value of type \c const_iterator.
    //!
    //! \exceptions
    //! \noexcept
    //!
    //! \sa cbegin_generators
    const_iterator cend_generators() const noexcept {
      return const_iterator(_gens.cend() - 1);
    }

    // forward decl, defined in konieczny.tpp
    template <typename T>
    struct DClassIteratorTraits;

    //! \brief  Return type of \ref cbegin_current_D_classes and
    //! \ref cend_current_D_classes.
    //!
    //! Type for const random access iterators through the
    //! \f$\mathscr{D}\f$-classes, in the order they were enumerated.
    //!
    //! \sa const_regular_d_class_iterator.
    using const_d_class_iterator
        = detail::ConstIteratorStateless<DClassIteratorTraits<DClass>>;

    //! \brief  Returns a const iterator referring to a pointer to the current
    //! first \f$\mathscr{D}\f$-class.
    //!
    //! This  function does not trigger any enumeration; the iterator returned
    //! may be invalidated by any call to a non-const member function of the
    //! Konieczny class.
    //!
    //! \returns
    //! A value of type \c const_d_class_iterator.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    // not noexcept because operator++ isn't necessarily
    const_d_class_iterator cbegin_current_D_classes() const {
      auto it = _D_classes.cbegin();
      if (_run_initialised) {
        return const_d_class_iterator(it)
               + (_adjoined_identity_contained ? 0 : 1);
      } else {
        return const_d_class_iterator(it);
      }
    }

    //! \brief  Returns a const iterator referring to past the pointer to the
    //! current last \f$\mathscr{D}\f$-class.
    //!
    //! This  function does not trigger any enumeration; the iterator returned
    //! may be invalidated by any call to a non-const member function of the
    //! Konieczny class.
    //!
    //! \returns
    //! A value of type \c const_d_class_iterator.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    const_d_class_iterator cend_current_D_classes() const noexcept {
      return const_d_class_iterator(_D_classes.cend());
    }

    //! \brief Returns a const iterator referring to a pointer to the first
    //! \f$\mathscr{D}\f$-class.
    //!
    //! This function triggers the main algorithm to run fully.
    //!
    //! \returns
    //! A value of type \c const_d_class_iterator.
    const_d_class_iterator cbegin_D_classes() {
      run();
      return cbegin_current_D_classes();
    }

    //! \brief Returns a const iterator referring to past the pointer to the
    //! last \f$\mathscr{D}\f$-class.
    //!
    //! This function triggers the main algorithm to run fully.
    //!
    //! \returns
    //! A value of type \c const_d_class_iterator.
    const_d_class_iterator cend_D_classes() {
      run();
      return cend_current_D_classes();
    }

    //! \brief  Return type of \ref cbegin_current_regular_D_classes and
    //! \ref cend_current_regular_D_classes.
    //!
    //! A type for const random access iterators through the regular
    //! \f$\mathscr{D}\f$-classes, in the order they were enumerated.
    //!
    //! \sa const_d_class_iterator.
    using const_regular_d_class_iterator
        = detail::ConstIteratorStateless<DClassIteratorTraits<RegularDClass>>;

    //! \brief  Returns a const iterator referring to a pointer to the current
    //! first regular \f$\mathscr{D}\f$-class.
    //!
    //! This  function does not trigger any enumeration; the iterator returned
    //! may be invalidated by any call to a non-const member function of the
    //! Konieczny class.
    //!
    //! \returns
    //! A value of type \c const_d_class_iterator.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    // not noexcept because operator++ isn't necessarily
    const_regular_d_class_iterator cbegin_current_regular_D_classes() const {
      auto it = _regular_D_classes.cbegin();
      if (_run_initialised) {
        return const_regular_d_class_iterator(it)
               + (_adjoined_identity_contained ? 0 : 1);
      } else {
        return const_regular_d_class_iterator(it);
      }
    }

    //! \brief  Returns a const iterator referring to past the pointer to the
    //! current last regular \f$\mathscr{D}\f$-class.
    //!
    //! This  function does not trigger any enumeration; the iterator returned
    //! may be invalidated by any call to a non-const member function of the
    //! Konieczny class.
    //!
    //! \returns
    //! A value of type \c const_d_class_iterator.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    const_regular_d_class_iterator
    cend_current_regular_D_classes() const noexcept {
      return const_regular_d_class_iterator(_regular_D_classes.cend());
    }

    //! \brief  Returns a const iterator referring to a pointer to the first
    //! regular \f$\mathscr{D}\f$-class.
    //!
    //! This function triggers the main algorithm to run fully.
    //!
    //! \returns
    //! A value of type \c const_d_class_iterator.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    //!
    const_regular_d_class_iterator cbegin_regular_D_classes() {
      run();
      return cbegin_current_regular_D_classes();
    }

    //! \brief  Returns a const iterator referring to past the pointer to the
    //! last regular \f$\mathscr{D}\f$-class.
    //!
    //! This function triggers the main algorithm to run fully.
    //!
    //! \returns
    //! A value of type \c const_d_class_iterator.
    //!
    //! \exceptions
    //! \no_libsemigroups_except
    const_regular_d_class_iterator cend_regular_D_classes() {
      run();
      return cend_current_regular_D_classes();
    }

   private:
    using PoolGuard = detail::PoolGuard<internal_element_type>;

    ////////////////////////////////////////////////////////////////////////
    // Konieczny - utility methods - private
    ////////////////////////////////////////////////////////////////////////

    // assumes its argument has valid lambda/rho values
    bool is_regular_element_no_checks(internal_const_reference x,
                                      lambda_orb_index_type    lpos = UNDEFINED,
                                      rho_orb_index_type rpos = UNDEFINED) {
      LIBSEMIGROUPS_ASSERT(_lambda_orb.finished() && _rho_orb.finished());
      lpos = lpos != UNDEFINED ? lpos : get_lpos(x);
      rpos = rpos != UNDEFINED ? rpos : get_rpos(x);
      return get_lambda_group_index(x, lpos, rpos) != UNDEFINED;
    }

    lambda_orb_index_type get_lpos(internal_const_reference x) const {
      Lambda()(_tmp_lambda_value1, this->to_external_const(x));
      return _lambda_orb.position(_tmp_lambda_value1);
    }

    rho_orb_index_type get_rpos(internal_const_reference x) const {
      Rho()(_tmp_rho_value1, this->to_external_const(x));
      return _rho_orb.position(_tmp_rho_value1);
    }

    // Returns a lambda orb index corresponding to a group H-class in the R-
    // class of \p x.
    // asserts its argument has lambda/rho values in the orbits.
    // modifies _tmp_lambda_value1
    // modifies _tmp_rho_value1
    lambda_orb_index_type get_lambda_group_index(internal_const_reference x,
                                                 lambda_orb_index_type    lpos
                                                 = UNDEFINED,
                                                 rho_orb_index_type rpos
                                                 = UNDEFINED);

    // Finds a group index of a H-class in the L-class of \p x.
    // modifies _tmp_lambda_value1
    // modifies _tmp_rho_value1
    rho_orb_index_type get_rho_group_index(internal_const_reference x,
                                           lambda_orb_index_type    lpos
                                           = UNDEFINED,
                                           rho_orb_index_type rpos = UNDEFINED);

    //! Finds the idempotent in the H-class of \p x. Note that it is assumed
    //! that \p x is in a group H-class.
    // TODO(later): it must be possible to do better than this
    void idem_in_H_class(internal_reference       res,
                         internal_const_reference x) const;

    //! Finds an idempotent in the \f$\mathscr{D}\f$-class of \c x, if \c x is
    //! regular, and modifies \c x in place to be this idempotent
    // modifies _tmp_lambda_value1
    void make_idem(internal_reference x);

    //! Finds the group inverse of \p x in its H-class; i.e. the element \c y
    //! in the H-class of \p x such that <tt> xy = \p id</tt>. Will run
    //! forever if no such element exists.
    void group_inverse(internal_element_type&   res,
                       internal_const_reference id,
                       internal_const_reference x) const;

    //! Determines whether <tt>(x, y)</tt> forms a group index.
    // modifies _tmp_lambda_value and _tmp_rho_value
    bool is_group_index(internal_const_reference x,
                        internal_const_reference y,
                        lambda_orb_index_type    lpos = UNDEFINED,
                        rho_orb_index_type       rpos = UNDEFINED) const;

    // pass full_check = true to use the contains method of the D-classes
    // instead of the contains_no_checks
    D_class_index_type get_containing_D_class(internal_const_reference x,
                                              bool const full_check = false);

    void add_to_D_maps(D_class_index_type d);

    ////////////////////////////////////////////////////////////////////////
    // Konieczny - accessor member functions - private
    ////////////////////////////////////////////////////////////////////////

    void add_D_class(RegularDClass* D);
    void add_D_class(NonRegularDClass* D);

    auto cbegin_internal_generators() const noexcept {
      return _gens.cbegin();
    }

    auto cend_internal_generators() const noexcept {
      return _gens.cend();
    }

    detail::Pool<internal_element_type>& element_pool() const {
      return _element_pool;
    }

    size_t max_rank() const noexcept {
      if (_ranks.empty()) {
        return UNDEFINED;
      }
      return *_ranks.rbegin();
    }

    ////////////////////////////////////////////////////////////////////////
    // Konieczny - initialisation member functions - private
    ////////////////////////////////////////////////////////////////////////

    void free_internals();

    void init_run();

    void init_data();

    void init_rank_state_and_rep_vecs();

    void compute_orbs();

    ////////////////////////////////////////////////////////////////////////
    // Konieczny - validation member functions - private
    ////////////////////////////////////////////////////////////////////////

    void throw_if_bad_degree(const_reference x) const {
      size_t const n = Degree()(x);
      if (degree() != UNDEFINED && n != degree()) {
        LIBSEMIGROUPS_EXCEPTION(
            "element has degree {} but should have degree {}", n, degree());
      }
    }

    template <typename Iterator>
    void throw_if_bad_degree(Iterator first, Iterator last) const;

    ////////////////////////////////////////////////////////////////////////
    // Konieczny - Runner methods - private
    ////////////////////////////////////////////////////////////////////////

    bool finished_impl() const override;
    void run_impl() override;
    void report_before_run();
    void report_progress();
    void report_after_run();

    ////////////////////////////////////////////////////////////////////////
    // Konieczny - data - private
    ////////////////////////////////////////////////////////////////////////

    bool                                         _adjoined_identity_contained;
    bool                                         _can_accept_generators;
    std::vector<DClass*>                         _D_classes;
    std::vector<std::vector<D_class_index_type>> _D_rels;
    bool                                         _data_initialised;
    size_t                                       _degree;
    mutable detail::Pool<internal_element_type>  _element_pool;
    std::vector<internal_element_type>           _gens;
    std::unordered_map<std::pair<rho_orb_index_type, lambda_orb_index_type>,
                       lambda_orb_index_type,
                       PairHash>
        _group_indices;
    std::unordered_map<std::pair<rho_orb_index_type, lambda_orb_index_type>,
                       rho_orb_index_type,
                       PairHash>
                    _group_indices_rev;
    lambda_orb_type _lambda_orb;
    std::unordered_map<lambda_orb_index_type, std::vector<D_class_index_type>>
                                      _lambda_to_D_map;
    std::vector<std::vector<RepInfo>> _nonregular_reps;
    internal_element_type             _one;
    rank_state_type*                  _rank_state;
    std::set<rank_type>               _ranks;
    std::vector<RegularDClass*>       _regular_D_classes;
    std::vector<std::vector<RepInfo>> _regular_reps;
    size_t                            _reps_processed;
    rho_orb_type                      _rho_orb;
    std::unordered_map<rho_orb_index_type, std::vector<D_class_index_type>>
                              _rho_to_D_map;
    bool                      _run_initialised;
    mutable lambda_value_type _tmp_lambda_value1;
    mutable lambda_value_type _tmp_lambda_value2;
    mutable rho_value_type    _tmp_rho_value1;
    mutable rho_value_type    _tmp_rho_value2;
  };  // class Konieczny

  template <typename Element, typename Traits>
  bool Konieczny<Element, Traits>::finished_impl() const {
    return _ranks.empty() && _run_initialised;
  }

  //! \ingroup konieczny_group
  //!
  //! \brief Return a human readable representation of a Konieczny object.
  //!
  //! Return a human readable representation of a Konieczny object.
  //!
  //! \param x the Konieczny object.
  //!
  //! \exceptions
  //! \no_libsemigroups_except
  template <typename Element, typename Traits>
  [[nodiscard]] std::string
  to_human_readable_repr(Konieczny<Element, Traits> const& x);

  //! \ingroup konieczny_group
  //!
  //! \brief Return a human readable representation of a Konieczny::DClass
  //! object.
  //!
  //! Return a human readable representation of a Konieczny::DClass object.
  //!
  //! \param x the Konieczny::DClass object.
  //!
  //! \exceptions
  //! \no_libsemigroups_except
  template <typename Element, typename Traits>
  [[nodiscard]] std::string
  to_human_readable_repr(typename Konieczny<Element, Traits>::DClass const& x);

  //! \ingroup konieczny_group
  //!
  //! \brief This namespace contains helper functions for the Konieczny class
  //! template.
  namespace konieczny {

    //! \brief  Add collection of generators from container.
    //!
    //! See \ref Konieczny::add_generator for a detailed description.
    //!
    //! \tparam T the type of the container for generators to add (must be a
    //! non-pointer type).
    //!
    //! \param K reference to the Konieczny instance to add generators to.
    //! \param coll the collection of generators to add.
    //!
    //! \throws LibsemigroupsException if any of the following hold:
    //! * the degree of \p x is incompatible with the existing degree.
    //! * \ref Konieczny::started returns \c true
    template <typename Element, typename Traits, typename T>
    void add_generators(Konieczny<Element, Traits> K, T const& coll) {
      static_assert(!std::is_pointer_v<T>,
                    "the template parameter T must not be a pointer");
      K.add_generators(coll.begin(), coll.end());
    }

    //! \brief  Add collection of generators from initializer list.
    //!
    //! See \ref Konieczny::add_generator for a detailed description.
    //!
    //! \param K reference to the Konieczny instance to add generators to.
    //! \param coll the collection of generators to add.
    //!
    //! \throws LibsemigroupsException if any of the following hold:
    //! * the degree of \p x is incompatible with the existing degree.
    //! * \ref Konieczny::started returns \c true
    template <typename Element, typename Traits>
    void add_generators(
        Konieczny<Element, Traits>& K,
        std::initializer_list<
            typename Konieczny<Element, Traits>::const_element_type> coll) {
      K.add_generators(coll.begin(), coll.end());
    }

  }  // namespace konieczny

  //! \defgroup make_konieczny_group make<Konieczny>
  //! \ingroup konieczny_group
  //!
  //! \brief Safely construct a \ref Konieczny instance.
  //!
  //! This page contains documentation related to safely constructing a
  //! \ref Konieczny instance.
  //!
  //! \sa \ref make_group for an overview of possible uses of the `make`
  //! function.

  //! \ingroup make_konieczny_group
  //!
  //! \brief Safely construct from generators.
  //!
  //! Despite the hideous signature, this function should be invoked as follows:
  //!
  //! \code_no_test
  //! make<Konieczny>(gens);
  //! \end_code_no_test
  //!
  //! This function constructs a Konieczny instance generated by the
  //! specified container of generators.  There can be duplicate generators
  //! and although they do not count as distinct elements, they do count as
  //! distinct generators.  In other words, the generators are precisely (a
  //! copy of) \p gens in the same order they occur in \p gens.
  //!
  //! \tparam Thing for SFINAE, must be Konieczny.
  //! \tparam Container the type of the argument \p gens.
  //! \tparam Traits the traits type for the Konieczny being constructed
  //! (defaults to `KoniecznyTraits<typename Container::value_type>`).
  //!
  //! \param gens the generators.
  //!
  //! \throws LibsemigroupsException if any of the following hold:
  //! * \p gens is empty
  //! * Degree`{}(x)` != Degree`{}(y)` for some \c x, \c y in \p gens.
  // TODO(1) to tpp
  template <template <typename...> typename Thing,
            typename Container,
            typename Traits = KoniecznyTraits<typename Container::value_type>>
  [[nodiscard]] std::enable_if_t<
      std::is_same_v<Konieczny<typename Container::value_type, Traits>,
                     Thing<typename Container::value_type, Traits>>,
      Konieczny<typename Container::value_type, Traits>>
  make(Container const& gens) {
    if (gens.size() == 0) {
      LIBSEMIGROUPS_EXCEPTION(
          "expected a positive number of generators, but got 0");
    }
    Konieczny<typename Container::value_type, Traits> result;
    result.add_generators(std::begin(gens), std::end(gens));
    return result;
  }

  //! \ingroup make_konieczny_group
  //!
  //! \brief Safely construct from generators.
  //!
  //! Despite the hideous signature, this function should be invoked as follows:
  //!
  //! \code_no_test
  //! make<Konieczny>(gens);
  //! \end_code_no_test
  //!
  //! This function constructs a Konieczny instance generated by the
  //! specified container of generators.  There can be duplicate generators
  //! and although they do not count as distinct elements, they do count as
  //! distinct generators.  In other words, the generators are precisely (a
  //! copy of) \p gens in the same order they occur in \p gens.
  //!
  //! \tparam Thing for SFINAE, must be Konieczny.
  //! \tparam Element the type of the elements in the Konieczny.
  //! \tparam Traits the traits type for the Konieczny being constructed
  //! (defaults to `KoniecznyTraits<Element>`).
  //!
  //! \param gens the generators.
  //!
  //! \throws LibsemigroupsException if any of the following hold:
  //! * \p gens is empty
  //! * Degree`{}(x)` != Degree`{}(y)` for some \c x, \c y in \p gens.
  template <template <typename...> typename Thing,
            typename Element,
            typename Traits = KoniecznyTraits<Element>>
  [[nodiscard]] std::enable_if_t<
      std::is_same_v<Konieczny<Element, Traits>, Thing<Element, Traits>>,
      Konieczny<Element, Traits>>
  make(std::initializer_list<Element> const& gens) {
    return make<Konieczny, std::initializer_list<Element>>(gens);
  }

}  // namespace libsemigroups
#include "konieczny.tpp"
#endif  // LIBSEMIGROUPS_KONIECZNY_HPP_
