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

// The purpose of this file is to test the CongruenceInterface class.

#include "catch.hpp"      // for REQUIRE
#include "test-main.hpp"  // for LIBSEMIGROUPS_TEST_CASE

#include "libsemigroups/cong-intf.hpp"     // for CongruenceInterface
#include "libsemigroups/cong-pair.hpp"     // for CongruenceByPairs
#include "libsemigroups/cong.hpp"          // for Congruence
#include "libsemigroups/fpsemi.hpp"        // for FpSemigroup
#include "libsemigroups/knuth-bendix.hpp"  // for KnuthBendix
#include "libsemigroups/tce.hpp"           // for TCE
#include "libsemigroups/todd-coxeter.hpp"  // for ToddCoxeter
#include "libsemigroups/transf.hpp"        // for Transf<>

CATCH_REGISTER_ENUM(libsemigroups::tril,
                    libsemigroups::tril::TRUE,
                    libsemigroups::tril::FALSE,
                    libsemigroups::tril::unknown);

namespace libsemigroups {
  struct LibsemigroupsException;  // Forward declaration

  constexpr bool REPORT              = false;
  congruence_kind constexpr twosided = congruence_kind::twosided;
  congruence_kind constexpr left     = congruence_kind::left;
  congruence_kind constexpr right    = congruence_kind::right;

  using detail::TCE;
  using FroidurePinTCE = FroidurePin<TCE, FroidurePinTraits<TCE, TCE::Table>>;

  namespace congruence {
    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "000",
                            "add_pair",
                            "[quick]") {
      auto rg = ReportGuard(REPORT);

      std::unique_ptr<CongruenceInterface> cong;

      SECTION("ToddCoxeter") {
        cong = std::make_unique<ToddCoxeter>(twosided);
      }
      SECTION("KnuthBendix") {
        cong = std::make_unique<KnuthBendix>();
      }
      SECTION("CongruenceByPairs") {
        ToddCoxeter tc(twosided);
        tc.set_number_of_generators(2);
        tc.add_pair({0, 0, 0}, {0});
        tc.add_pair({1, 1, 1, 1}, {1});
        tc.add_pair({0, 1, 0, 1}, {0, 0});
        cong = std::make_unique<CongruenceByPairs<FroidurePinTCE>>(
            right, tc.quotient_froidure_pin());
        // FIXME(when CongruenceByPairs has a proper traits class) We can't use
        // "twosided" here because TCE's products are only defined on the right
        // by generators, and CongruenceByPairs(twosided) does left and right
        // multiplication by generators. When CongruenceByPairs has a proper
        // traits class we can define Product to be the product in
        // tc.quotient_froidure_pin and via operator*.
      }
      SECTION("Congruence") {
        FpSemigroup S;  // free semigroup
        S.set_alphabet(2);
        cong = std::make_unique<Congruence>(twosided, S);
      }

      cong->set_number_of_generators(2);
      cong->add_pair({0, 0, 0}, {0});
      cong->add_pair({1, 1, 1, 1}, {1});
      cong->add_pair({0, 1, 0, 1}, {0, 0});

      REQUIRE(!cong->finished());
      REQUIRE(cong->number_of_classes() == 27);
      REQUIRE(cong->finished());
      REQUIRE(cong->started());
      REQUIRE_THROWS_AS(cong->add_pair({0}, {1}), LibsemigroupsException);
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "001",
                            "contains/const_contains",
                            "[quick]") {
      auto rg = ReportGuard(REPORT);

      std::unique_ptr<CongruenceInterface> cong;
      SECTION("no parent") {
        SECTION("ToddCoxeter") {
          cong = std::make_unique<ToddCoxeter>(twosided);
        }
        SECTION("KnuthBendix") {
          cong = std::make_unique<KnuthBendix>();
        }
        REQUIRE_THROWS_AS(cong->const_contains({0}, {1}),
                          LibsemigroupsException);
        REQUIRE_THROWS_AS(cong->const_contains({0}, {0}),
                          LibsemigroupsException);
      }
      SECTION("parent") {
        SECTION("Congruence") {
          FpSemigroup S;  // free semigroup
          S.set_alphabet(2);
          cong = std::make_unique<Congruence>(twosided, S);
        }
        SECTION("CongruenceByPairs") {
          ToddCoxeter tc(twosided);
          tc.set_number_of_generators(2);
          tc.add_pair({0, 0, 0}, {0});
          tc.add_pair({1, 1, 1, 1}, {1});
          tc.add_pair({0, 1, 0, 1}, {0, 0});
          cong = std::make_unique<CongruenceByPairs<FroidurePinTCE>>(
              right, tc.quotient_froidure_pin());
          // FIXME(when CongruenceByPairs has a proper traits class) We can't
          // use "twosided" here because TCE's products are only defined on the
          // right by generators, and CongruenceByPairs(twosided) does left and
          // right multiplication by generators. When CongruenceByPairs has a
          // proper traits class we can define Product to be the product in
          // tc.quotient_froidure_pin and via operator*.
          REQUIRE(cong->const_contains({0}, {1}) == tril::unknown);
        }
        REQUIRE(cong->contains({0}, {0}));
      }

      REQUIRE(!cong->finished());
      REQUIRE(!cong->started());

      cong->set_number_of_generators(2);
      cong->add_pair({0, 0, 0}, {0});
      cong->add_pair({1, 1, 1, 1}, {1});
      cong->add_pair({0, 1, 0, 1}, {0, 0});

      REQUIRE(cong->const_contains({0, 0, 0}, {0, 0}) == tril::unknown);
      REQUIRE(!cong->contains({0, 0, 0}, {0, 0}));
      REQUIRE(cong->const_contains({0, 0, 0}, {0, 0}) == tril::FALSE);
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "002",
                            "less",
                            "[quick][no-valgrind]") {
      auto rg = ReportGuard(REPORT);

      std::unique_ptr<CongruenceInterface> cong;

      SECTION("ToddCoxeter") {
        cong = std::make_unique<ToddCoxeter>(twosided);
        REQUIRE_THROWS_AS(cong->less({0}, {1}), LibsemigroupsException);
        REQUIRE(!cong->has_parent_froidure_pin());
      }
      SECTION("KnuthBendix") {
        cong = std::make_unique<KnuthBendix>();
        REQUIRE_THROWS_AS(cong->less({0}, {1}), LibsemigroupsException);
        REQUIRE(!cong->has_parent_froidure_pin());
      }
      SECTION("CongruenceByPairs") {
        ToddCoxeter tc(twosided);
        tc.set_number_of_generators(2);
        tc.add_pair({0, 0, 0}, {0});
        tc.add_pair({1, 1, 1, 1}, {1});
        tc.add_pair({0, 1, 0, 1}, {0, 0});
        cong = std::make_unique<CongruenceByPairs<FroidurePinTCE>>(
            right, tc.quotient_froidure_pin());
        // FIXME(when CongruenceByPairs has a proper traits class) We can't use
        // "twosided" here because TCE's products are only defined on the right
        // by generators, and CongruenceByPairs(twosided) does left and right
        // multiplication by generators. When CongruenceByPairs has a proper
        // traits class we can define Product to be the product in
        // tc.quotient_froidure_pin and via operator*.

        // Wouldn't throw because of the parent semigroup, commented out anyway
        // so can add more pairs below
        // REQUIRE_NOTHROW(cong->less({0}, {1}));
        REQUIRE(cong->has_parent_froidure_pin());
      }
      SECTION("Congruence") {
        FpSemigroup S;  // free semigroup
        S.set_alphabet(2);
        cong = std::make_unique<Congruence>(twosided, S);
      }

      cong->set_number_of_generators(2);
      cong->add_pair({0, 0, 0}, {0});
      cong->add_pair({1, 1, 1, 1}, {1});
      cong->add_pair({0, 1, 0, 1}, {0, 0});

      REQUIRE(cong->less({0}, {1}));
      REQUIRE(cong->number_of_classes() == 27);
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "003",
                            "is_quotient_obviously_(in)finite",
                            "[quick]") {
      auto rg = ReportGuard(REPORT);

      std::unique_ptr<CongruenceInterface> cong;

      SECTION("ToddCoxeter") {
        cong = std::make_unique<ToddCoxeter>(twosided);
      }
      SECTION("KnuthBendix") {
        cong = std::make_unique<KnuthBendix>();
      }

      REQUIRE(!cong->is_quotient_obviously_infinite());
      // So far cong is not-defined, and hence not finite or infinite
      REQUIRE(!cong->is_quotient_obviously_finite());

      cong->set_number_of_generators(2);
      REQUIRE(cong->is_quotient_obviously_infinite());
      REQUIRE(!cong->is_quotient_obviously_finite());

      cong->add_pair({0, 0, 0}, {0});
      cong->add_pair({1, 1, 1, 1}, {1});
      cong->add_pair({0, 1, 0, 1}, {0, 0});

      REQUIRE(!cong->is_quotient_obviously_infinite());
      REQUIRE(!cong->is_quotient_obviously_finite());

      REQUIRE(cong->number_of_classes() == 27);
      REQUIRE(!cong->is_quotient_obviously_infinite());
      REQUIRE(cong->is_quotient_obviously_finite());
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "004",
                            "is_quotient_obviously_(in)finite",
                            "[quick]") {
      auto rg = ReportGuard(REPORT);

      std::unique_ptr<CongruenceInterface> cong;
      SECTION("CongruenceByPairs") {
        ToddCoxeter tc(twosided);
        tc.set_number_of_generators(2);
        tc.add_pair({0, 0, 0}, {0});
        tc.add_pair({1, 1, 1, 1}, {1});
        tc.add_pair({0, 1, 0, 1}, {0, 0});
        cong = std::make_unique<CongruenceByPairs<FroidurePinTCE>>(
            right, tc.quotient_froidure_pin());
      }
      SECTION("Congruence") {
        FpSemigroup S;
        S.set_alphabet(2);
        S.add_rule({0, 0, 0}, {0});
        S.add_rule({1, 1, 1, 1}, {1});
        S.add_rule({0, 1, 0, 1}, {0, 0});
        REQUIRE(!S.is_obviously_infinite());
        REQUIRE(!S.is_obviously_finite());
        cong = std::make_unique<Congruence>(right, S);

        REQUIRE_THROWS_AS(cong->set_number_of_generators(3),
                          LibsemigroupsException);
      }

      cong->add_pair({0, 0, 0}, {0, 0});

      REQUIRE(!cong->is_quotient_obviously_infinite());
      REQUIRE(!cong->is_quotient_obviously_finite());
      REQUIRE(cong->number_of_classes() == 24);
      REQUIRE(!cong->is_quotient_obviously_infinite());
      REQUIRE(cong->is_quotient_obviously_finite());
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "005",
                            "parent_froidure_pin",
                            "[quick]") {
      auto rg = ReportGuard(REPORT);

      std::unique_ptr<CongruenceInterface> cong;

      SECTION("ToddCoxeter") {
        cong = std::make_unique<ToddCoxeter>(twosided);
      }
      SECTION("KnuthBendix") {
        cong = std::make_unique<KnuthBendix>();
      }
      REQUIRE_THROWS_AS(cong->parent_froidure_pin(), LibsemigroupsException);
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "006",
                            "parent_froidure_pin",
                            "[quick]") {
      auto rg = ReportGuard(REPORT);

      std::unique_ptr<CongruenceInterface> cong;
      SECTION("CongruenceByPairs") {
        ToddCoxeter tc(twosided);
        tc.set_number_of_generators(2);
        tc.add_pair({0, 0, 0}, {0});
        tc.add_pair({1, 1, 1, 1}, {1});
        tc.add_pair({0, 1, 0, 1}, {0, 0});
        cong = std::make_unique<CongruenceByPairs<FroidurePinTCE>>(
            left, tc.quotient_froidure_pin());
      }

      SECTION("Congruence") {
        FpSemigroup S;
        S.set_alphabet(2);
        S.add_rule({0, 0, 0}, {0});
        S.add_rule({1, 1, 1, 1}, {1});
        S.add_rule({0, 1, 0, 1}, {0, 0});
        REQUIRE(!S.is_obviously_infinite());
        REQUIRE_NOTHROW(S.froidure_pin());
        cong = std::make_unique<Congruence>(left, S);
        REQUIRE_THROWS_AS(cong->set_number_of_generators(3),
                          LibsemigroupsException);
      }

      cong->add_pair({0, 0, 0}, {0, 0});

      REQUIRE(cong->has_parent_froidure_pin());
      REQUIRE(cong->parent_froidure_pin()->size() == 27);
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "007",
                            "cbegin/cend_ntc",
                            "[quick]") {
      auto rg = ReportGuard(REPORT);
      auto S  = FroidurePin<Transf<>>(
          {Transf<>({1, 3, 4, 2, 3}), Transf<>({3, 2, 1, 3, 3})});

      std::unique_ptr<CongruenceInterface> cong;

      REQUIRE(S.size() == 88);
      REQUIRE(S.number_of_rules() == 18);

      SECTION("ToddCoxeter") {
        cong = std::make_unique<ToddCoxeter>(twosided, S);
      }
      SECTION("KnuthBendix") {
        cong = std::make_unique<KnuthBendix>(S);
      }

      cong->add_pair(S.factorisation(Transf<>({3, 4, 4, 4, 4})),
                     S.factorisation(Transf<>({3, 1, 3, 3, 3})));

      REQUIRE(cong->number_of_classes() == 21);
      REQUIRE(cong->number_of_non_trivial_classes() == 1);
      REQUIRE(cong->cbegin_ntc()->size() == 68);
      std::vector<word_type> expect = {{0, 0, 1},
                                       {1, 0, 1},
                                       {0, 0, 0, 1},
                                       {0, 0, 1, 0},
                                       {0, 0, 1, 1},
                                       {0, 1, 0, 1},
                                       {1, 0, 0, 1},
                                       {1, 0, 1, 0},
                                       {1, 0, 1, 1},
                                       {0, 0, 0, 0, 1},
                                       {0, 0, 0, 1, 0},
                                       {0, 0, 0, 1, 1},
                                       {0, 0, 1, 0, 0},
                                       {0, 0, 1, 0, 1},
                                       {0, 0, 1, 1, 0},
                                       {0, 1, 0, 1, 0},
                                       {0, 1, 0, 1, 1},
                                       {1, 0, 0, 0, 1},
                                       {1, 0, 0, 1, 0},
                                       {1, 0, 0, 1, 1},
                                       {1, 0, 1, 0, 0},
                                       {1, 0, 1, 0, 1},
                                       {1, 0, 1, 1, 0},
                                       {0, 0, 0, 0, 1, 0},
                                       {0, 0, 0, 0, 1, 1},
                                       {0, 0, 0, 1, 0, 0},
                                       {0, 0, 0, 1, 0, 1},
                                       {0, 0, 0, 1, 1, 0},
                                       {0, 0, 1, 0, 0, 0},
                                       {0, 0, 1, 1, 0, 0},
                                       {0, 1, 0, 0, 0, 1},
                                       {0, 1, 0, 1, 0, 0},
                                       {0, 1, 0, 1, 0, 1},
                                       {0, 1, 0, 1, 1, 0},
                                       {1, 0, 0, 0, 1, 0},
                                       {1, 0, 0, 0, 1, 1},
                                       {1, 0, 0, 1, 0, 0},
                                       {1, 0, 0, 1, 0, 1},
                                       {1, 0, 0, 1, 1, 0},
                                       {1, 0, 1, 0, 0, 0},
                                       {1, 0, 1, 1, 0, 0},
                                       {0, 0, 0, 0, 1, 0, 0},
                                       {0, 0, 0, 0, 1, 0, 1},
                                       {0, 0, 0, 0, 1, 1, 0},
                                       {0, 0, 0, 1, 0, 0, 0},
                                       {0, 0, 0, 1, 1, 0, 0},
                                       {0, 0, 1, 0, 0, 0, 1},
                                       {0, 1, 0, 0, 0, 1, 0},
                                       {0, 1, 0, 0, 0, 1, 1},
                                       {0, 1, 0, 1, 0, 0, 0},
                                       {0, 1, 0, 1, 1, 0, 0},
                                       {1, 0, 0, 0, 1, 0, 0},
                                       {1, 0, 0, 0, 1, 0, 1},
                                       {1, 0, 0, 0, 1, 1, 0},
                                       {1, 0, 0, 1, 0, 0, 0},
                                       {1, 0, 0, 1, 1, 0, 0},
                                       {0, 0, 0, 0, 1, 0, 0, 0},
                                       {0, 0, 0, 0, 1, 1, 0, 0},
                                       {0, 0, 1, 0, 0, 0, 1, 0},
                                       {0, 1, 0, 0, 0, 1, 0, 0},
                                       {0, 1, 0, 0, 0, 1, 0, 1},
                                       {0, 1, 0, 0, 0, 1, 1, 0},
                                       {1, 0, 0, 0, 1, 0, 0, 0},
                                       {1, 0, 0, 0, 1, 1, 0, 0},
                                       {0, 0, 1, 0, 0, 0, 1, 0, 0},
                                       {0, 0, 1, 0, 0, 0, 1, 0, 1},
                                       {0, 1, 0, 0, 0, 1, 0, 0, 0},
                                       {0, 1, 0, 0, 0, 1, 1, 0, 0}};
      auto                   it     = cong->cbegin_ntc();
      REQUIRE(it + 1 == cong->cend_ntc());
      REQUIRE(std::vector<word_type>(it->cbegin(), it->cend()) == expect);
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "008",
                            "cbegin/cend_ntc",
                            "[quick]") {
      auto rg = ReportGuard(REPORT);
      auto S  = FroidurePin<Transf<>>(
          {Transf<>({1, 3, 4, 2, 3}), Transf<>({3, 2, 1, 3, 3})});

      std::unique_ptr<CongruenceInterface> cong;

      REQUIRE(S.size() == 88);
      REQUIRE(S.number_of_rules() == 18);

      SECTION("CongruenceByPairs") {
        cong = std::make_unique<CongruenceByPairs<decltype(S)>>(right, S);
      }
      SECTION("Congruence") {
        cong = std::make_unique<Congruence>(right, S);
      }
      cong->add_pair(S.factorisation(Transf<>({3, 4, 4, 4, 4})),
                     S.factorisation(Transf<>({3, 1, 3, 3, 3})));

      REQUIRE(cong->number_of_classes() == 72);
      REQUIRE(cong->number_of_non_trivial_classes() == 4);
      std::vector<size_t> actual(4, 0);
      std::transform(cong->cbegin_ntc(),
                     cong->cend_ntc(),
                     actual.begin(),
                     std::mem_fn(&std::vector<word_type>::size));
      std::sort(actual.begin(), actual.end());
      std::vector<size_t> expect = {3, 5, 5, 7};
      REQUIRE(actual == expect);
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "009",
                            "quotient is immutable",
                            "[quick]") {
      auto rg = ReportGuard(REPORT);

      std::unique_ptr<CongruenceInterface> cong;

      SECTION("ToddCoxeter") {
        cong = std::make_unique<ToddCoxeter>(twosided);
      }
      SECTION("KnuthBendix") {
        cong = std::make_unique<KnuthBendix>();
      }
      // TODO(later) not yet implemented
      // SECTION("CongruenceByPairs") {
      //   cong =
      //   std::make_unique<CongruenceByPairs<decltype(S)::element_type>>(twosided,
      //   S);
      // }
      SECTION("Congruence") {
        cong = std::make_unique<Congruence>(twosided);
      }

      cong->set_number_of_generators(2);
      cong->add_pair({0, 0, 0}, {0});
      cong->add_pair({1, 1, 1, 1}, {1});
      cong->add_pair({0, 1, 0, 1}, {0, 0});

      REQUIRE(cong->quotient_froidure_pin()->immutable());
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "010",
                            "no generating pairs added",
                            "[quick][cong]") {
      auto                                 rg = ReportGuard(REPORT);
      std::unique_ptr<CongruenceInterface> cong;
      SECTION("common behaviour") {
        SECTION("KnuthBendix") {
          cong = std::make_unique<KnuthBendix>();
          cong->set_number_of_generators(4);
          // KnuthBendix can find the class index, but the others can't
        }
        SECTION("Congruence") {
          cong = std::make_unique<Congruence>(twosided);
          cong->set_number_of_generators(4);
          static_cast<Congruence*>(cong.get())->max_threads(2);
          REQUIRE(cong->const_contains({1}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2})
                  == tril::FALSE);
        }
        REQUIRE(cong->word_to_class_index({2, 2, 2, 2}) == 254);
        REQUIRE(cong->class_index_to_word(2) == word_type({2}));
      }
      SECTION("ToddCoxeter") {
        cong = std::make_unique<ToddCoxeter>(twosided);
        cong->set_number_of_generators(4);
        REQUIRE(cong->const_contains({1}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2})
                == tril::unknown);
        REQUIRE_THROWS_AS(cong->word_to_class_index({2, 2, 2, 2}),
                          LibsemigroupsException);
        REQUIRE_THROWS_AS(cong->class_index_to_word(2), LibsemigroupsException);
      }
      REQUIRE(!cong->contains({1}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2}));
      REQUIRE(cong->number_of_classes() == POSITIVE_INFINITY);
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "011",
                            "nr generators not set",
                            "[quick][cong]") {
      auto                                 rg = ReportGuard(REPORT);
      std::unique_ptr<CongruenceInterface> cong;
      SECTION("ToddCoxeter") {
        cong = std::make_unique<ToddCoxeter>(twosided);
      }
      SECTION("KnuthBendix") {
        cong = std::make_unique<KnuthBendix>();
      }
      SECTION("Congruence") {
        cong = std::make_unique<Congruence>(twosided);
      }
      REQUIRE_THROWS_AS(cong->set_number_of_generators(0),
                        LibsemigroupsException);
      REQUIRE_THROWS_AS(
          cong->const_contains({1}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2}),
          LibsemigroupsException);
      REQUIRE_THROWS_AS(cong->contains({1}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2}),
                        LibsemigroupsException);
      REQUIRE(cong->number_of_classes() == UNDEFINED);
      REQUIRE_THROWS_AS(cong->word_to_class_index({2, 2, 2, 2}),
                        LibsemigroupsException);
      REQUIRE_THROWS_AS(cong->class_index_to_word(0), LibsemigroupsException);
      REQUIRE_THROWS_AS(cong->class_index_to_word(1), LibsemigroupsException);
      REQUIRE_THROWS_AS(cong->class_index_to_word(2), LibsemigroupsException);
      REQUIRE_THROWS_AS(cong->run(), LibsemigroupsException);
    }

    LIBSEMIGROUPS_TEST_CASE("CongruenceInterface",
                            "012",
                            "no generating pairs",
                            "[quick][cong]") {
      auto rg = ReportGuard(REPORT);
      auto S  = FroidurePin<Transf<>>({Transf<>({1, 3, 4, 2, 3}),
                                       Transf<>({1, 3, 4, 2, 3}),
                                       Transf<>({3, 2, 1, 3, 3})});

      REQUIRE(S.size() == 88);
      REQUIRE(S.number_of_rules() == 21);

      CongruenceByPairs<decltype(S)> cong(twosided, S);
      REQUIRE_THROWS_AS(cong.set_number_of_generators(0),
                        LibsemigroupsException);
      REQUIRE(cong.word_to_class_index({2, 2, 2, 2}) == 0);
      REQUIRE(cong.const_contains({1}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2})
              == tril::FALSE);
      REQUIRE(cong.class_index_to_word(2) == word_type({2}));
      REQUIRE(!cong.contains({1}, {2, 2, 2, 2, 2, 2, 2, 2, 2, 2}));
      REQUIRE(cong.number_of_classes() == 88);
    }
  }  // namespace congruence
}  // namespace libsemigroups
