//
// Semigroups package for GAP
// Copyright (C) 2022 James D. Mitchell
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
// This file contains a function LATTICE_OF_CONGRUENCES for finding the lattice
// of congruences when there are too many generating congruences for
// Froidure-Pin to handle.
#include "conglatt.hpp"
#include <algorithm>
// for equal, max
#include <chrono>
// for time_point
#include <cmath>
// for log2
#include <cstddef>
// for size_t
#include <cstdint>
// for uint16_t, uint32_t
#include <iostream>
// for cout
#include <memory>
// for unique_ptr
#include <numeric>
// for iota
#include <unordered_map>
// for unordered_map
#include <utility>
// for swap, pair
#include <vector>
// for vector
// GAP headers
#include "gap_all.h"
// Semigroups package for GAP headers
#include "semigroups-debug.hpp" // for SEMIGROUPS_ASSERT
// libsemigroups headers
#include "libsemigroups/adapters.hpp" // for Hash
#include "libsemigroups/report.hpp" // for should_report
#include "libsemigroups/string.hpp" // for group_digits
//
namespace semigroups {
namespace {
// This class is minimally adapted from libsemigroups::detail::UF, but
// specialised for the problem at hand.
template <
typename T>
class UF {
std::vector<T> _data;
size_t _log2_data_size;
public:
////////////////////////////////////////////////////////////////////////
// Aliases - public
////////////////////////////////////////////////////////////////////////
using size_type = size_t;
using node_type = T;
using container_type = std::vector<T>;
using index_type = T;
// not noexcept because the constructors of std::vector and std::array
// aren't
explicit UF(size_type size)
: _data(size, 0),
_log2_data_size(
std::max(
static_cast<size_t>(std::log2(_data.size())),
static_cast<size_t>(1))) {
SEMIGROUPS_ASSERT(size != 0);
std::iota(_data.begin(), _data.end(), 0);
}
// not noexcept because the constructors of std::vector and std::array
// aren't
UF(UF
const&) =
default;
UF&
operator=(UF
const&) =
default;
UF(UF&&) =
default;
UF&
operator=(UF&&) =
default;
~UF() =
default;
// not noexcept because std::vector::operator[] isn't
index_type find(index_type x)
const {
SEMIGROUPS_ASSERT(x < _data.size());
auto y = _data[x];
while (y != _data[y]) {
y = _data[y];
}
return y;
}
// not noexcept because UF::find isn't
void unite(index_type x, index_type y) {
SEMIGROUPS_ASSERT(x < _data.size());
SEMIGROUPS_ASSERT(y < _data.size());
x = find(x);
y = find(y);
if (x < y) {
_data[y] = x;
}
else {
_data[x] = y;
}
}
// Not noexcept because std::equal isn't
bool operator==(UF
const& that)
const {
return std::equal(that._data.cbegin(),
that._data.cend(),
_data.cbegin(),
_data.cend());
}
void normalize() {
for (index_type i = 0; i < _data.size(); ++i) {
_data[i] = find(_data[i]);
}
}
void join(UF
const& x, UF
const& y) {
SEMIGROUPS_ASSERT(size() == x.size());
SEMIGROUPS_ASSERT(size() == y.size());
for (index_type i = 0; i < _data.size(); ++i) {
_data[i] = x._data[i];
unite(x._data[i], y._data[i]);
}
normalize();
}
size_t hash()
const {
size_t val = 0;
for (
auto it = _data.cbegin(); it < _data.cend();
it += _log2_data_size) {
val ^= *it + 0x9e3779b97f4a7c16 + (val << 6);
}
return val;
}
size_t size()
const noexcept {
return _data.size();
}
};
}
// namespace
}
// namespace semigroups
namespace libsemigroups {
template <
typename T>
struct Hash<semigroups::UF<T>*> {
size_t
operator()(semigroups::UF<T>* x)
const {
return x->hash();
}
};
template <
typename T>
struct Hash<semigroups::UF<T>> {
size_t
operator()(semigroups::UF<T>
const& x)
const {
return x.hash();
}
};
template <
typename T>
struct EqualTo<semigroups::UF<T>*> {
size_t
operator()(semigroups::UF<T>* x, semigroups::UF<T>* y)
const {
return *x == *y;
}
};
}
// namespace libsemigroups
namespace semigroups {
namespace {
// should be to_cpp<UF>
auto to_uf(Obj lookup) {
using UF = UF<uint16_t>;
SEMIGROUPS_ASSERT(IS_LIST(lookup));
size_t
const n = LEN_LIST(lookup);
SEMIGROUPS_ASSERT(n < 65536);
UF uf(n);
for (uint16_t i = 0; i < n; ++i) {
SEMIGROUPS_ASSERT(IS_INTOBJ(ELM_LIST(lookup, i)));
SEMIGROUPS_ASSERT(INT_INTOBJ(ELM_LIST(lookup, i)) >= 1);
SEMIGROUPS_ASSERT(INT_INTOBJ(ELM_LIST(lookup, i)) <= n);
uf.unite(i, INT_INTOBJ(ELM_LIST(lookup, i + 1)) - 1);
}
return uf;
}
}
// namespace
Obj LATTICE_OF_CONGRUENCES(Obj list) {
using UF = UF<uint16_t>;
using libsemigroups::EqualTo;
using libsemigroups::Hash;
using libsemigroups::detail::group_digits;
using std::chrono::duration_cast;
using std::chrono::nanoseconds;
using std::chrono::seconds;
if (LEN_LIST(list) == 0) {
ErrorQuit(
"the argument must be a list of length at least 1, found 0", 0L, 0L);
}
size_t
const n = LEN_LIST(ELM_LIST(list, 1));
if (n > 65535) {
// Then the values in the lookup won't fit into uint16_t
ErrorQuit(
"the lists in the argument must have length at most 65535, "
"found %d",
(
Int) LEN_LIST(list),
0L);
}
auto start_time = std::chrono::high_resolution_clock::now();
auto last_report = start_time;
uint32_t last_count = 1;
bool report = libsemigroups::report::should_report();
std::vector<UF> gens;
gens.reserve(LEN_LIST(list));
for (size_t i = 1; i <= LEN_LIST(list); ++i) {
gens.push_back(to_uf(ELM_LIST(list, i)));
}
std::unordered_map<UF*, uint32_t, Hash<UF*>, EqualTo<UF*>> res;
Obj latt = NEW_PLIST(T_PLIST_TAB_RECT, 1);
AssPlist(latt, 1, NEW_PLIST(T_PLIST_CYC, gens.size()));
auto one = std::make_unique<UF>(n);
auto tmp = std::make_unique<UF>(n);
std::vector<std::unique_ptr<UF>> todo;
res.emplace(one.get(), 0);
todo.push_back(std::move(one));
for (size_t i = 0; i < todo.size(); ++i) {
size_t
const old_todo_size = todo.size();
for (size_t j = 0; j < gens.size(); ++j) {
auto const& g = gens[j];
tmp->join(*todo[i], g);
auto it = res.find(tmp.get());
if (it == res.end()) {
auto cpy = std::make_unique<UF>(*tmp);
res.emplace(cpy.get(), todo.size());
AssPlist(ELM_PLIST(latt, i + 1), j + 1, INTOBJ_INT(todo.size() + 1));
todo.push_back(std::move(cpy));
}
else {
AssPlist(ELM_PLIST(latt, i + 1), j + 1, INTOBJ_INT(it->second + 1));
}
}
for (size_t k = old_todo_size; k < todo.size(); ++k) {
PushPlist(latt, NEW_PLIST(T_PLIST_CYC, gens.size()));
}
if (report) {
auto now = std::chrono::high_resolution_clock::now();
if (now - last_report > std::chrono::seconds(1)) {
auto total_time = duration_cast<seconds>(now - start_time);
auto diff_time = duration_cast<seconds>(now - last_report);
std::cout <<
"#I Found " << group_digits(res.size())
<<
" congruences in " << total_time.count() <<
"s ("
<< group_digits((todo.size() - last_count)
/ diff_time.count())
<<
"/s)!\n";
std::swap(now, last_report);
last_count = todo.size();
}
}
}
return latt;
}
}
// namespace semigroups