//
// 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/>.
//
#include "libsemigroups/cong-intf.hpp"
#include <algorithm>
// for remove_if
#include "libsemigroups/constants.hpp" // for UNDEFINED
#include "libsemigroups/debug.hpp" // for LIBSEMIGROUPS_ASSERT
#include "libsemigroups/exception.hpp" // for LIBSEMIGROUPS_EXCEPTION
#include "libsemigroups/fpsemi-intf.hpp" // for FpSemigroupInterface
#include "libsemigroups/froidure-pin-base.hpp" // for FroidurePinBase
#include "libsemigroups/report.hpp" // for REPORT_VERBOSE_DEFAULT
#include "libsemigroups/string.hpp" // for detail::to_string
namespace libsemigroups {
class CongruenceInterface::LazyFroidurePin {
public:
LazyFroidurePin() =
default;
void set(std::shared_ptr<FroidurePinBase> ptr) {
LIBSEMIGROUPS_ASSERT(_froidure_pin == nullptr);
LIBSEMIGROUPS_ASSERT(_fp_semigroup == nullptr);
_froidure_pin = ptr;
}
void set(std::shared_ptr<FpSemigroupInterface> ptr) {
LIBSEMIGROUPS_ASSERT(_froidure_pin == nullptr);
LIBSEMIGROUPS_ASSERT(_fp_semigroup == nullptr);
_fp_semigroup = ptr;
}
bool has_froidure_pin()
const noexcept {
return _froidure_pin != nullptr;
}
bool has_fpsemigroup()
const noexcept {
return _fp_semigroup != nullptr;
}
bool can_compute_froidure_pin()
const noexcept {
return _froidure_pin != nullptr || _fp_semigroup != nullptr;
}
std::shared_ptr<FroidurePinBase> froidure_pin()
const {
if (!has_froidure_pin()) {
if (_fp_semigroup != nullptr
&& !_fp_semigroup->is_obviously_infinite()) {
_froidure_pin = _fp_semigroup->froidure_pin();
}
else {
// This can be the case if a CongruenceInterface is default
// constructed, i.e. constructed neither from a FroidurePinBase
// nor an FpSemigroupInterface, or the _fp_semigroup is obviously
// infinite
LIBSEMIGROUPS_EXCEPTION(
"no parent FroidurePin can be determined!");
}
}
return _froidure_pin;
}
std::shared_ptr<FpSemigroupInterface> fpsemigroup()
const {
return _fp_semigroup;
}
private:
mutable std::shared_ptr<FroidurePinBase> _froidure_pin;
mutable std::shared_ptr<FpSemigroupInterface> _fp_semigroup;
};
////////////////////////////////////////////////////////////////////////////
// CongruenceInterface - constructors + destructor - public
////////////////////////////////////////////////////////////////////////////
CongruenceInterface::CongruenceInterface(congruence_kind type)
: Runner(),
// Non-mutable
_gen_pairs(),
_nr_gens(UNDEFINED),
_parent(std::make_shared<LazyFroidurePin>()),
_type(type),
// Mutable
_init_ntc_done(),
_is_obviously_finite(
false),
_is_obviously_infinite(
false),
_quotient(nullptr),
_non_trivial_classes() {
reset();
}
CongruenceInterface::~CongruenceInterface() =
default;
////////////////////////////////////////////////////////////////////////////
// Runner - non-pure virtual overridden function - public
////////////////////////////////////////////////////////////////////////////
void CongruenceInterface::before_run() {
if (number_of_generators() == UNDEFINED) {
LIBSEMIGROUPS_EXCEPTION(
"no generators have been set!");
}
}
////////////////////////////////////////////////////////////////////////////
// CongruenceInterface - non-pure virtual methods - public
////////////////////////////////////////////////////////////////////////////
tril CongruenceInterface::const_contains(word_type
const& u,
word_type
const& v)
const {
validate_word(u);
validate_word(v);
if (u == v) {
return tril::
TRUE;
}
class_index_type uu, vv;
try {
uu = const_word_to_class_index(u);
vv = const_word_to_class_index(v);
}
catch (LibsemigroupsException
const& e) {
REPORT_VERBOSE_DEFAULT(
"ignoring exception:\n%s", e.what());
return tril::unknown;
}
if (uu == UNDEFINED || vv == UNDEFINED) {
return tril::unknown;
}
else if (uu == vv) {
return tril::
TRUE;
}
else if (finished()) {
return tril::
FALSE;
}
else {
return tril::unknown;
}
}
void CongruenceInterface::set_number_of_generators(size_t n) {
if (number_of_generators() != UNDEFINED) {
if (number_of_generators() != n) {
LIBSEMIGROUPS_EXCEPTION(
"cannot change the number of generators");
}
else {
return;
// do nothing
}
}
else if (n == 0) {
LIBSEMIGROUPS_EXCEPTION(
"the number of generators must be non-zero!");
}
else if (started()) {
LIBSEMIGROUPS_EXCEPTION(
"cannot set the number of generator at this stage");
}
_nr_gens = n;
set_number_of_generators_impl(n);
reset();
}
/////////////////////////////////////////////////////////////////////////
// CongruenceInterface - non-virtual methods - public
/////////////////////////////////////////////////////////////////////////
void CongruenceInterface::add_pair(word_type
const& u, word_type
const& v) {
if (started()) {
LIBSEMIGROUPS_EXCEPTION(
"cannot add further generating pairs at this stage");
}
validate_word(u);
validate_word(v);
if (u == v) {
return;
}
else if (has_parent_froidure_pin()
&& parent_froidure_pin()->equal_to(u, v)) {
return;
}
// Note that _gen_pairs might contain pairs of distinct words that
// represent the same element of the parent semigroup (if any).
_gen_pairs.emplace_back(u, v);
add_pair_impl(u, v);
reset();
}
word_type CongruenceInterface::class_index_to_word(class_index_type i) {
if (number_of_generators() == UNDEFINED) {
LIBSEMIGROUPS_EXCEPTION(
"no generators have been defined");
}
else if (i >= number_of_classes()) {
LIBSEMIGROUPS_EXCEPTION(
"invalid class index, expected a value in the "
"range [0, %d), found %d",
number_of_classes(),
i);
}
return class_index_to_word_impl(i);
}
// Basic exception guarantee (since is_quotient_obviously_infinite() may
// change the object).
std::shared_ptr<FroidurePinBase>
CongruenceInterface::quotient_froidure_pin() {
if (_quotient != nullptr) {
LIBSEMIGROUPS_ASSERT(kind() == congruence_kind::twosided);
return _quotient;
}
else if (kind() != congruence_kind::twosided) {
LIBSEMIGROUPS_EXCEPTION(
"the congruence must be two-sided");
}
_quotient = quotient_impl();
_quotient->immutable(
true);
return _quotient;
}
bool CongruenceInterface::is_quotient_obviously_infinite() {
// If has_parent_froidure_pin(), then that is either finite (and so this
// is not obviously infinite), or infinite, which is undecidable in
// general, so we leave the answer to this question to
// is_quotient_obviously_infinite_impl in the derived class.
if (number_of_generators() == UNDEFINED) {
// If number_of_generators() is undefined, then there is no quotient yet,
// and so it is not obviously infinite, or anything!
REPORT_VERBOSE(
"not obviously infinite (no generators yet defined)");
return false;
}
else if (has_quotient_froidure_pin()
&& quotient_froidure_pin()->finished()) {
// If the quotient FroidurePin is fully enumerated, it must be
// finite, and hence this is not (obviously) infinite.
REPORT_VERBOSE(
"not obviously infinite (finite)");
return false;
}
else if (has_parent_froidure_pin() && parent_froidure_pin()->finished()) {
REPORT_VERBOSE(
"not obviously infinite (parent finite)");
return false;
}
else if (is_quotient_obviously_infinite_impl()) {
// The derived class of CongruenceInterface knows the quotient is
// infinite
return true;
}
REPORT_VERBOSE(
"the quotient is not obviously infinite . . .");
return false;
}
bool CongruenceInterface::is_quotient_obviously_finite() {
if ((has_quotient_froidure_pin() && quotient_froidure_pin()->finished())
|| (has_parent_froidure_pin() && parent_froidure_pin()->finished())
|| is_quotient_obviously_finite_impl()) {
return true;
}
return false;
}
size_t CongruenceInterface::number_of_classes() {
if (number_of_generators() == UNDEFINED) {
return UNDEFINED;
}
else if (!finished() && is_quotient_obviously_infinite()) {
return POSITIVE_INFINITY;
}
return number_of_classes_impl();
}
CongruenceInterface::class_index_type
CongruenceInterface::word_to_class_index(word_type
const& word) {
// validate_word throws if number_of_generators is undefined.
validate_word(word);
return word_to_class_index_impl(word);
}
/////////////////////////////////////////////////////////////////////////
// CongruenceInterface - non-virtual methods - protected
/////////////////////////////////////////////////////////////////////////
void CongruenceInterface::set_parent_froidure_pin(
std::shared_ptr<FroidurePinBase> prnt) {
LIBSEMIGROUPS_ASSERT(!_parent->has_froidure_pin());
LIBSEMIGROUPS_ASSERT(number_of_generators() == UNDEFINED
|| prnt->number_of_generators()
== number_of_generators());
LIBSEMIGROUPS_ASSERT(!finished());
if (number_of_generators() == UNDEFINED) {
set_number_of_generators(prnt->number_of_generators());
}
_parent->set(prnt);
reset();
}
void CongruenceInterface::set_parent_froidure_pin(
std::shared_ptr<FpSemigroupInterface> prnt) {
LIBSEMIGROUPS_ASSERT(!_parent->has_froidure_pin());
LIBSEMIGROUPS_ASSERT(number_of_generators() == UNDEFINED
|| prnt->alphabet().size() == number_of_generators());
LIBSEMIGROUPS_ASSERT(!finished());
if (number_of_generators() == UNDEFINED && !prnt->alphabet().empty()) {
set_number_of_generators(prnt->alphabet().size());
}
_parent->set(prnt);
reset();
}
void CongruenceInterface::add_generators(size_t n) {
if (n == 0) {
return;
}
else if (started()) {
LIBSEMIGROUPS_EXCEPTION(
"cannot add generators at this stage");
}
_nr_gens += n;
add_generators_impl(_nr_gens);
reset();
}
/////////////////////////////////////////////////////////////////////////
// CongruenceInterface - non-pure virtual methods - private
/////////////////////////////////////////////////////////////////////////
void CongruenceInterface::add_pair_impl(word_type
const&, word_type
const&) {}
CongruenceInterface::class_index_type
CongruenceInterface::const_word_to_class_index(word_type
const&)
const {
return UNDEFINED;
}
void CongruenceInterface::set_number_of_generators_impl(size_t) {
// do nothing
}
void CongruenceInterface::add_generators_impl(size_t) {
// do nothing
}
std::shared_ptr<CongruenceInterface::non_trivial_classes_type
const>
CongruenceInterface::non_trivial_classes_impl() {
if (!_parent->can_compute_froidure_pin()) {
LIBSEMIGROUPS_EXCEPTION(
"Cannot determine the parent FroidurePin and so "
"cannot compute non-trivial classes!");
}
// The next line may trigger an infinite computation
auto fp = _parent->froidure_pin();
auto ntc = non_trivial_classes_type(number_of_classes(),
std::vector<word_type>());
word_type w;
for (size_t pos = 0; pos < fp->size(); ++pos) {
fp->factorisation(w, pos);
LIBSEMIGROUPS_ASSERT(word_to_class_index(w) < ntc.size());
ntc[word_to_class_index(w)].push_back(w);
}
ntc.erase(std::remove_if(ntc.begin(),
ntc.end(),
[](std::vector<word_type>
const& klass) ->
bool {
return klass.size() <= 1;
}),
ntc.end());
return std::make_shared<non_trivial_classes_type>(ntc);
}
/////////////////////////////////////////////////////////////////////////
// CongruenceInterface - non-virtual methods - private
/////////////////////////////////////////////////////////////////////////
void CongruenceInterface::init_non_trivial_classes() {
if (!_init_ntc_done) {
_non_trivial_classes = non_trivial_classes_impl();
_init_ntc_done =
true;
}
}
void CongruenceInterface::reset() noexcept {
// set_finished(false);
_non_trivial_classes.reset();
_init_ntc_done =
false;
_quotient.reset();
_is_obviously_finite =
false;
_is_obviously_infinite =
false;
}
std::shared_ptr<FroidurePinBase>
CongruenceInterface::parent_froidure_pin()
const {
return _parent->froidure_pin();
}
bool CongruenceInterface::has_parent_froidure_pin()
const noexcept {
return _parent->has_froidure_pin();
}
std::shared_ptr<FpSemigroupInterface>
CongruenceInterface::parent_fpsemigroup()
const {
return _parent->fpsemigroup();
}
bool CongruenceInterface::has_parent_fpsemigroup()
const noexcept {
return _parent->has_fpsemigroup();
}
/////////////////////////////////////////////////////////////////////////
// CongruenceInterface - non-virtual methods - protected
/////////////////////////////////////////////////////////////////////////
bool CongruenceInterface::validate_letter(letter_type c)
const {
if (number_of_generators() == UNDEFINED) {
LIBSEMIGROUPS_EXCEPTION(
"no generators have been defined");
}
return c < _nr_gens;
}
void CongruenceInterface::validate_word(word_type
const& w)
const {
for (
auto l : w) {
// validate_letter throws if no generators are defined
if (!validate_letter(l)) {
LIBSEMIGROUPS_EXCEPTION(
"letter index out of bounds in word %s, expected "
"value in [0, %d), got %d",
detail::to_string(w).c_str(),
_nr_gens,
l);
}
}
}
/////////////////////////////////////////////////////////////////////////
// CongruenceInterface - non-virtual static methods - protected
/////////////////////////////////////////////////////////////////////////
std::string
const&
CongruenceInterface::congruence_kind_to_string(congruence_kind typ) {
switch (typ) {
case congruence_kind::twosided:
return STRING_TWOSIDED;
case congruence_kind::left:
return STRING_LEFT;
case congruence_kind::right:
return STRING_RIGHT;
default:
LIBSEMIGROUPS_EXCEPTION(
"incorrect type");
}
}
/////////////////////////////////////////////////////////////////////////
// CongruenceInterface - static data members - private
/////////////////////////////////////////////////////////////////////////
const std::string CongruenceInterface::STRING_TWOSIDED =
"two-sided";
const std::string CongruenceInterface::STRING_LEFT =
"left";
const std::string CongruenceInterface::STRING_RIGHT =
"right";
}
// namespace libsemigroups