// // libsemigroups - C++ library for semigroups and monoids // Copyright (C) 2020 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 <algorithm> // for min #include <array> // for array #include <bitset> // for bitset #include <cstddef> // for size_t #include <iosfwd> // for ostringstream #include <numeric> // for inner_product #include <type_traits> // for false_type, is_signed, true_type #include <unordered_map> // for unordered_map #include <unordered_set> // for unordered_set #include <vector> // for vector
#include"adapters.hpp"// for Degree #include"bitset.hpp"// for BitSet #include"constants.hpp"// for POSITIVE_INFINITY #include"containers.hpp"// for StaticVector1 #include"debug.hpp"// for LIBSEMIGROUPS_ASSERT #include"exception.hpp"// for LIBSEMIGROUPS_EXCEPTION #include"string.hpp"// for detail::to_string
using scalar_type = typename Container::value_type; using scalar_reference = typename Container::reference; using scalar_const_reference = typename Container::const_reference; using semiring_type = Semiring;
using container_type = Container; using iterator = typename Container::iterator; using const_iterator = typename Container::const_iterator;
explicit MatrixCommon(std::vector<std::vector<scalar_type>> const& m)
: MatrixCommon() {
init(m);
}
MatrixCommon(
std::initializer_list<std::initializer_list<scalar_type>> const& m)
: MatrixCommon() {
init(m);
}
private: // not noexcept because resize isn't template <typename T> void init(T const& m) {
size_t const R = m.size(); if (R == 0) { return;
}
size_t const C = m.begin()->size();
resize(R, C); for (size_t r = 0; r < R; ++r) { auto row = m.begin() + r; for (size_t c = 0; c < C; ++c) {
_container[r * C + c] = *(row->begin() + c);
}
}
}
// not noexcept because init isn't void
init(std::initializer_list<std::initializer_list<scalar_type>> const& m) {
init<std::initializer_list<std::initializer_list<scalar_type>>>(m);
}
static Subclass make(voidconst*,
std::initializer_list<scalar_type> const& il) { return make(il);
}
virtual ~MatrixCommon() = default;
// not noexcept because mem allocate is required
Subclass identity() const {
size_t const n = number_of_rows();
Subclass x(semiring(), n, n);
std::fill(x.begin(), x.end(), zero()); for (size_t r = 0; r < n; ++r) {
x(r, r) = one();
} return x;
}
// not noexcept because vector::operator[] isn't, and neither is // array::operator[]
scalar_reference operator()(size_t r, size_t c) { return this->_container[r * number_of_cols() + c];
}
// not noexcept because vector::operator[] isn't, and neither is // array::operator[]
scalar_const_reference operator()(size_t r, size_t c) const { return this->_container[r * number_of_cols() + c];
} // noexcept because number_of_rows_impl is noexcept
size_t number_of_rows() const noexcept { returnstatic_cast<Subclass const*>(this)->number_of_rows_impl();
}
// noexcept because number_of_cols_impl is noexcept
size_t number_of_cols() const noexcept { returnstatic_cast<Subclass const*>(this)->number_of_cols_impl();
}
// not noexcept because Hash<T>::operator() isn't
size_t hash_value() const { return Hash<Container>()(_container);
}
// not noexcept because memory is allocated void product_inplace(Subclass const& A, Subclass const& B) {
LIBSEMIGROUPS_ASSERT(number_of_rows() == number_of_cols());
LIBSEMIGROUPS_ASSERT(A.number_of_rows() == number_of_rows());
LIBSEMIGROUPS_ASSERT(B.number_of_rows() == number_of_rows());
LIBSEMIGROUPS_ASSERT(A.number_of_cols() == number_of_cols());
LIBSEMIGROUPS_ASSERT(B.number_of_cols() == number_of_cols());
LIBSEMIGROUPS_ASSERT(&A != this);
LIBSEMIGROUPS_ASSERT(&B != this);
// Benchmarking boolean matrix multiplication reveals that using a // non-static container_type gives the best performance, when compared // to static container_type the performance is more or less the same // (but not thread-safe), and there appears to be a performance // penalty of about 50% when using static thread_local container_type // (when compiling with clang).
size_t const N = A.number_of_rows();
std::vector<scalar_type> tmp(N, 0);
for (size_t c = 0; c < N; c++) { for (size_t i = 0; i < N; i++) {
tmp[i] = B(i, c);
} for (size_t r = 0; r < N; r++) {
(*this)(r, c) = std::inner_product(
A._container.begin() + r * N,
A._container.begin() + (r + 1) * N,
tmp.begin(),
zero(),
[this](scalar_type x, scalar_type y) { return this->plus(x, y);
},
[this](scalar_type x, scalar_type y) { return this->prod(x, y);
});
}
}
}
// not noexcept because iterator increment isn't voidoperator*=(scalar_type a) { for (auto it = _container.begin(); it < _container.end(); ++it) {
*it = prod(*it, a);
}
}
// not noexcept because vector::operator[] and array::operator[] aren't voidoperator+=(Subclass const& that) {
LIBSEMIGROUPS_ASSERT(that.number_of_rows() == number_of_rows());
LIBSEMIGROUPS_ASSERT(that.number_of_cols() == number_of_cols()); for (size_t i = 0; i < _container.size(); ++i) {
_container[i] = plus(_container[i], that._container[i]);
}
}
// TODO(later) uncomment and test (this works, just not tested or used // for anything, so because time is short commenting out for now) // void operator+=(scalar_type a) { // for (auto it = _container.begin(); it < _container.end(); ++it) { // *it = plus(*it, a); // } // }
// noexcept because vector::begin and array::begin are noexcept
iterator begin() noexcept { return _container.begin();
}
// noexcept because vector::end and array::end are noexcept
iterator end() noexcept { return _container.end();
}
// noexcept because vector::begin and array::begin are noexcept
const_iterator begin() const noexcept { return _container.begin();
}
// noexcept because vector::end and array::end are noexcept
const_iterator end() const noexcept { return _container.end();
}
// noexcept because vector::cbegin and array::cbegin are noexcept
const_iterator cbegin() const noexcept { return _container.cbegin();
}
// noexcept because vector::cend and array::cend are noexcept
const_iterator cend() const noexcept { return _container.cend();
}
template <typename U>
std::pair<scalar_type, scalar_type> coords(U const& it) const {
static_assert(
std::is_same<U, iterator>::value
|| std::is_same<U, const_iterator>::value, "the parameter it must be of type iterator or const_iterator");
scalar_type const v = std::distance(_container.begin(), it); return std::make_pair(v / number_of_cols(), v % number_of_cols());
}
// noexcept because vector::swap and array::swap are noexcept void swap(MatrixCommon& that) noexcept {
std::swap(_container, that._container);
}
// noexcept because swap is noexcept, and so too are number_of_rows and // number_of_cols void transpose() noexcept {
LIBSEMIGROUPS_ASSERT(number_of_rows() == number_of_cols()); if (number_of_rows() == 0) { return;
} auto& x = *this; for (size_t r = 0; r < number_of_rows() - 1; ++r) { for (size_t c = r + 1; c < number_of_cols(); ++c) {
std::swap(x(r, c), x(c, r));
}
}
}
// not noexcept because there's an allocation
RowView row(size_t i) const { if (i >= number_of_rows()) {
LIBSEMIGROUPS_EXCEPTION( "index out of range, expected value in [%llu, %llu), found %llu", static_cast<uint64_t>(0), static_cast<uint64_t>(number_of_rows()), static_cast<uint64_t>(i));
} auto& container = const_cast<Container&>(_container); return RowView(static_cast<Subclass const*>(this),
container.begin() + i * number_of_cols(),
number_of_cols());
}
//////////////////////////////////////////////////////////////////////// // RowViews - class for cheaply storing iterators to rows ////////////////////////////////////////////////////////////////////////
template <typename Mat, typename Subclass> class RowViewCommon {
static_assert(IsMatrix<Mat>, "the template parameter Mat must be derived from " "MatrixPolymorphicBase");
public: using const_iterator = typename Mat::const_iterator; using iterator = typename Mat::iterator;
using scalar_type = typename Mat::scalar_type; using scalar_reference = typename Mat::scalar_reference; using scalar_const_reference = typename Mat::scalar_const_reference;
using Row = typename Mat::Row; using matrix_type = Mat;
// not noexcept because operator[] isn't voidoperator+=(RowViewCommon const& x) { auto& this_ = *this; for (size_t i = 0; i < size(); ++i) {
this_[i] = plus(this_[i], x[i]);
}
}
// not noexcept because iterator arithmeic isn't voidoperator+=(scalar_type a) { for (auto& x : *this) {
x = plus(x, a);
}
}
// not noexcept because iterator arithmeic isn't voidoperator*=(scalar_type a) { for (auto& x : *this) {
x = prod(x, a);
}
}
// not noexcept because operator*= isn't
Row operator*(scalar_type a) const {
Row result(*static_cast<Subclass const*>(this));
result *= a; return result;
}
// not noexcept because operator+= isn't
Row operator+(RowViewCommon const& that) const {
Row result(*static_cast<Subclass const*>(this));
result += static_cast<Subclass const&>(that); return result;
}
//////////////////////////////////////////////////////////////////////// // StaticMatrix with compile time semiring arithmetic ////////////////////////////////////////////////////////////////////////
public: //////////////////////////////////////////////////////////////////////// // StaticMatrix - Aliases - public ////////////////////////////////////////////////////////////////////////
using scalar_type = typename MatrixCommon::scalar_type; using scalar_reference = typename MatrixCommon::scalar_reference; using scalar_const_reference = typename MatrixCommon::scalar_const_reference;
using Row = StaticMatrix<PlusOp, ProdOp, ZeroOp, OneOp, 1, C, Scalar>; using RowView = StaticRowView<PlusOp, ProdOp, ZeroOp, OneOp, C, Scalar>;
using Plus = PlusOp; using Prod = ProdOp; using Zero = ZeroOp; using One = OneOp;
explicit StaticMatrix(std::vector<std::vector<scalar_type>> const& m)
: MatrixCommon(m) {}
explicit StaticMatrix(RowView const& rv) : MatrixCommon(rv) {
static_assert(
R == 1, "cannot construct Matrix with more than one row from RowView!");
}
// For uniformity of interface, the first arg does nothing
StaticMatrix(voidconst* ptr, std::initializer_list<scalar_type> const& c)
: StaticMatrix(c) {
(void) ptr;
LIBSEMIGROUPS_ASSERT(ptr == nullptr);
}
// For uniformity of interface, the first arg does nothing
StaticMatrix( voidconst* ptr,
std::initializer_list<std::initializer_list<scalar_type>> const& m)
: StaticMatrix(m) {
(void) ptr;
LIBSEMIGROUPS_ASSERT(ptr == nullptr);
}
// For uniformity of interface, the first arg does nothing explicit StaticMatrix(voidconst* ptr, RowView const& rv)
: StaticMatrix(rv) {
(void) ptr;
LIBSEMIGROUPS_ASSERT(ptr == nullptr);
}
// For uniformity of interface, no arg used for anything
StaticMatrix(voidconst* ptr, size_t r, size_t c) : StaticMatrix(r, c) {
(void) ptr;
LIBSEMIGROUPS_ASSERT(ptr == nullptr);
}
~StaticMatrix() = default;
static StaticMatrix identity(size_t n = 0) {
static_assert(C == R, "cannot create non-square identity matrix"); // If specified the value of n must equal R or otherwise weirdness will // ensue...
LIBSEMIGROUPS_ASSERT(n == 0 || n == R);
(void) n; #ifdefined(__APPLE__) && defined(__clang__) \
&& (__clang_major__ == 13 || __clang_major__ == 14) // With Apple clang version 13.1.6 (clang-1316.0.21.2.5) something goes // wrong and the value R is optimized away somehow, meaning that the // values on the diagonal aren't actually set. This only occurs when // libsemigroups is compiled with -O2 or higher.
size_t volatile m = R; #else
size_t m = R; #endif
StaticMatrix x(m, m);
std::fill(x.begin(), x.end(), ZeroOp()()); for (size_t r = 0; r < m; ++r) {
x(r, r) = OneOp()();
} return x;
}
//////////////////////////////////////////////////////////////////////// // StaticMatrix - member function aliases - public ////////////////////////////////////////////////////////////////////////
using MatrixCommon::operator(); using MatrixCommon::begin; using MatrixCommon::number_of_cols; using MatrixCommon::number_of_rows; using MatrixCommon::operator==; using MatrixCommon::operator!=; using MatrixCommon::swap;
//////////////////////////////////////////////////////////////////////// // DynamicMatrix with compile time semiring arithmetic ////////////////////////////////////////////////////////////////////////
template <typename PlusOp, typename ProdOp, typename ZeroOp, typename OneOp, typename Scalar> class DynamicMatrix<PlusOp, ProdOp, ZeroOp, OneOp, Scalar> final
: public detail::MatrixDynamicDim<Scalar>, public detail::MatrixCommon<
std::vector<Scalar>,
DynamicMatrix<PlusOp, ProdOp, ZeroOp, OneOp, Scalar>,
DynamicRowView<PlusOp, ProdOp, ZeroOp, OneOp, Scalar>>, public detail::
MatrixStaticArithmetic<PlusOp, ProdOp, ZeroOp, OneOp, Scalar> { using MatrixDynamicDim = ::libsemigroups::detail::MatrixDynamicDim<Scalar>; using MatrixCommon = ::libsemigroups::detail::MatrixCommon<
std::vector<Scalar>,
DynamicMatrix<PlusOp, ProdOp, ZeroOp, OneOp, Scalar>,
DynamicRowView<PlusOp, ProdOp, ZeroOp, OneOp, Scalar>>; friend MatrixCommon;
public: using scalar_type = typename MatrixCommon::scalar_type; using scalar_reference = typename MatrixCommon::scalar_reference; using scalar_const_reference = typename MatrixCommon::scalar_const_reference;
using Row = DynamicMatrix; using RowView = DynamicRowView<PlusOp, ProdOp, ZeroOp, OneOp, Scalar>;
// For uniformity of interface, the first arg does nothing
DynamicMatrix(voidconst* ptr, size_t r, size_t c) : DynamicMatrix(r, c) {
(void) ptr;
LIBSEMIGROUPS_ASSERT(ptr == nullptr);
}
// For uniformity of interface, the first arg does nothing
DynamicMatrix(voidconst* ptr, std::initializer_list<scalar_type> const& c)
: DynamicMatrix(c) {
(void) ptr;
LIBSEMIGROUPS_ASSERT(ptr == nullptr);
}
// For uniformity of interface, the first arg does nothing
DynamicMatrix( voidconst* ptr,
std::initializer_list<std::initializer_list<scalar_type>> const& m)
: DynamicMatrix(m) {
(void) ptr;
LIBSEMIGROUPS_ASSERT(ptr == nullptr);
}
static DynamicMatrix identity(size_t n) {
DynamicMatrix x(n, n);
std::fill(x.begin(), x.end(), ZeroOp()()); for (size_t r = 0; r < n; ++r) {
x(r, r) = OneOp()();
} return x;
}
using MatrixCommon::begin; using MatrixCommon::identity; using MatrixCommon::number_of_cols; using MatrixCommon::number_of_rows; using MatrixCommon::resize;
static DynamicMatrix make(Semiring const* sr,
std::initializer_list<scalar_type> const il) {
DynamicMatrix m(sr, il);
validate(m); return m;
}
// No static DynamicMatrix::identity(size_t n) because we need a semiring! static DynamicMatrix identity(Semiring const* sr, size_t n) {
DynamicMatrix x(sr, n, n);
std::fill(x.begin(), x.end(), x.zero()); for (size_t r = 0; r < n; ++r) {
x(r, r) = x.one();
} return x;
}
~DynamicMatrix();
using MatrixCommon::begin; using MatrixCommon::identity; using MatrixCommon::number_of_cols; using MatrixCommon::number_of_rows; using MatrixCommon::resize;
//////////////////////////////////////////////////////////////////////// // Helper structs to check if matrix is static, or has a pointer to a // semiring ////////////////////////////////////////////////////////////////////////
// The use of `int` rather than `bool` as the scalar type for dynamic // boolean matrices is intentional, because the bit iterators implemented in // std::vector<bool> entail a significant performance penalty. using DynamicBMat
= DynamicMatrix<BooleanPlus, BooleanProd, BooleanZero, BooleanOne, int>;
// FLS + JDM considered adding BMat8 and decided it wasn't a good idea. template <size_t R = 0, size_t C = R> using BMat
= std::conditional_t<R == 0 || C == 0, DynamicBMat, StaticBMat<R, C>>;
template <size_t R, size_t C> struct BitSetCapacity<StaticBMat<R, C>> {
static_assert(R == C, "the number of rows and columns must be equal"); static constexpr size_t value = R;
};
} // namespace detail
template <typename Mat> auto validate(Mat const&) -> std::enable_if_t<IsMinPlusMat<Mat>> {}
//////////////////////////////////////////////////////////////////////// // Max-plus matrices with threshold ////////////////////////////////////////////////////////////////////////
template <size_t T, typename Scalar> struct MaxPlusTruncProd {
static_assert(std::is_signed<Scalar>::value, "MaxPlus requires a signed integer type as parameter!");
Scalar operator()(Scalar x, Scalar y) const noexcept {
LIBSEMIGROUPS_ASSERT((x >= 0 && x <= static_cast<Scalar>(T))
|| x == NEGATIVE_INFINITY);
LIBSEMIGROUPS_ASSERT((y >= 0 && y <= static_cast<Scalar>(T))
|| y == NEGATIVE_INFINITY); if (x == NEGATIVE_INFINITY || y == NEGATIVE_INFINITY) { return NEGATIVE_INFINITY;
} return std::min(x + y, static_cast<Scalar>(T));
}
};
template <typename Scalar = int> class MaxPlusTruncSemiring final {
static_assert(std::is_signed<Scalar>::value, "MaxPlus requires a signed integer type as parameter!");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.