diff --git a/docs/DoxygenLayout.xml b/docs/DoxygenLayout.xml index 37b52e177..aa7591576 100644 --- a/docs/DoxygenLayout.xml +++ b/docs/DoxygenLayout.xml @@ -50,8 +50,8 @@ - - + + diff --git a/docs/libsemigroups.bib b/docs/libsemigroups.bib index b12adbb06..17a76cff1 100644 --- a/docs/libsemigroups.bib +++ b/docs/libsemigroups.bib @@ -7,6 +7,22 @@ %% Saved with string encoding Unicode (UTF-8) +@article{Gab00, + Author = {Harold N. Gabow}, + Doi = {https://dx.doi.org/10.1016/S0020-0190(00)00051-X}, + Issn = {0020-0190}, + Journal = {Information Processing Letters}, + Keywords = {Algorithms}, + Number = {34}, + Pages = {107 - 114}, + Title = {Path-based depth-first search for strong and biconnected + components}, + Url = {https://www.sciencedirect.com/science/article/pii/S002001900000051X}, + Volume = {74}, + Year = {2000}, + Bdsk-Url-2 = {https://dx.doi.org/10.1016/S0020-0190(00)00051-X}, + Bdsk-Url-1 = {https://www.sciencedirect.com/science/article/pii/S002001900000051X}, +} @misc{Anagnostopoulou-Merkouri2023aa, author = {Anagnostopoulou-Merkouri, Marina and Mitchell, James D. and Tsalakou, Maria}, diff --git a/include/libsemigroups/action.tpp b/include/libsemigroups/action.tpp index 226988363..1aecef54d 100644 --- a/include/libsemigroups/action.tpp +++ b/include/libsemigroups/action.tpp @@ -221,7 +221,7 @@ namespace libsemigroups { if (started() && old_nr_gens < _gens.size()) { // Generators were added after the last call to run if (_pos > 0 && old_nr_gens < _gens.size()) { - _scc.reset(); + _scc.init(_graph); } for (size_t i = 0; i < _pos; i++) { for (size_t j = old_nr_gens; j < _gens.size(); ++j) { @@ -241,7 +241,7 @@ namespace libsemigroups { } } if (_pos < _orb.size() && !_gens.empty()) { - _scc.reset(); + _scc.init(_graph); } detail::Timer t; diff --git a/include/libsemigroups/cutting.hpp b/include/libsemigroups/cutting.hpp index cd1888a45..adbd8e655 100644 --- a/include/libsemigroups/cutting.hpp +++ b/include/libsemigroups/cutting.hpp @@ -41,11 +41,33 @@ #ifndef LIBSEMIGROUPS_CUTTING_HPP_ #define LIBSEMIGROUPS_CUTTING_HPP_ -#include "cong-intf.hpp" // for CongruenceInterface -#include "gabow.hpp" -#include "runner.hpp" // for Runner -#include "stephen.hpp" -#include "types.hpp" +#include // for for_each, copy +#include // for size_t +#include // for uint32_t, uin... +#include // for shared_ptr +#include // for operator+ +#include // for tie +#include // for operator== +#include // for swap, make_pair +#include // for vector + +#include "constants.hpp" // for operator!= +#include "gabow.hpp" // for Gabow +#include "presentation.hpp" // for InversePresen... +#include "ranges.hpp" // for begin, end +#include "runner.hpp" // for Runner +#include "stephen.hpp" // for Stephen +#include "types.hpp" // for word_type +#include "word-graph-with-sources.hpp" // for WordGraphWith... +#include "word-graph.hpp" // for WordGraph +#include "word-graph.hpp" // for standardize + +#include "detail/fmt.hpp" // for format, basic... +#include "detail/int-range.hpp" // for IntRange<>::v... +#include "detail/iterator.hpp" // for operator+ +#include "detail/node-managed-graph.hpp" // for NodeManagedGr... +#include "detail/node-manager.hpp" // for NodeManager::... +#include "detail/report.hpp" // for Ticker::Ticker namespace libsemigroups { class Cutting : public Runner { @@ -69,7 +91,7 @@ namespace libsemigroups { _stephens(), _finished(false), _graph(0, p.alphabet().size()), - _gabow() { + _gabow(_graph) { _presentation->validate(); _presentation->contains_empty_word(true); // TODO _stephens.emplace_back(_presentation); diff --git a/include/libsemigroups/gabow.hpp b/include/libsemigroups/gabow.hpp index c1937a63b..490816de6 100644 --- a/include/libsemigroups/gabow.hpp +++ b/include/libsemigroups/gabow.hpp @@ -22,24 +22,55 @@ #ifndef LIBSEMIGROUPS_GABOW_HPP_ #define LIBSEMIGROUPS_GABOW_HPP_ -#include // for size_t -#include // for queue -#include // for stack -#include // for vector +#include // for size_t +#include // for pair +#include // for queue +#include // for stack +#include // for string +#include // for vector +#include "constants.hpp" // for UNDEFINED, operator!=, Undefined +#include "debug.hpp" // for LIBSEMIGROUPS_ASSERT +#include "exception.hpp" // for LIBSEMIGROUPS_EXCEPTION #include "forest.hpp" // for Forest +#include "ranges.hpp" // for iterator_range, transform #include "word-graph.hpp" // for WordGraph -#include "ranges.hpp" // for iterator_range, transform - namespace libsemigroups { + //! \defgroup gabow_group Gabow + //! + //! This page contains information about the implementation in + //! ``libsemigroups`` of Gabow's algorithm \cite Gab00 for computing the + //! strongly connected components of a WordGraph. + + //! \ingroup gabow_group + //! + //! \brief Class implementing Gabow's algorithm for computing strongly + //! connected components of a WordGraph. + //! + //! Defined in ``gabow.hpp``. + //! + //! Instances of this class can be used to compute, and provide information + //! about, the strongly connected components of the WordGraph used to + //! construct the instance. The strongly connected components are + //! lazily evaluated when triggered by a relevant member function. The + //! complexity of Gabow's algorithm is at most \f$O(mn)\f$ where \c m is + //! WordGraph::number_of_nodes() and \c n is \ref WordGraph::out_degree(). + //! + //! \tparam Node the type of the nodes of the underlying + //! WordGraph. template class Gabow { public: - using node_type = Node; + //! Type of the nodes in the underlying WordGraph. + using node_type = Node; + + //! Type of the edge labels in the underlying WordGraph. using label_type = typename WordGraph::label_type; - using size_type = size_t; + + //! Size type used for indices of strongly connected components. + using size_type = size_t; private: WordGraph const* _graph; @@ -52,241 +83,409 @@ namespace libsemigroups { mutable bool _forwd_forest_defined; public: - Gabow() = default; - Gabow(Gabow const&) = default; - Gabow(Gabow&&) = default; + //! \brief Deleted. + //! + //! To avoid the situation where the underlying WordGraph is not defined, it + //! is not possible to default construct a Gabow object. + Gabow() = delete; + + //! \brief Default copy constructor. + //! + //! Default copy constructor. + Gabow(Gabow const&) = default; + + //! \brief Default move constructor. + //! + //! Default move constructor. + Gabow(Gabow&&) = default; + + //! \brief Default copy assignment operator. + //! + //! Default copy assignment operator. Gabow& operator=(Gabow const&) = default; - Gabow& operator=(Gabow&&) = default; + + //! \brief Default move assignment operator. + //! + //! Default move assignment operator. + Gabow& operator=(Gabow&&) = default; + ~Gabow(); - explicit Gabow(WordGraph const& wg) : Gabow() { + //! \brief Construct from WordGraph + //! + //! This function constructs a Gabow object from the WordGraph \p wg. + //! + //! \warning The Gabow object only holds a reference to the underlying + //! WordGraph \p wg, and so that object must outlive the corresponding Gabow + //! object. + //! + //! \note This function does not trigger the computation of the strongly + //! connected components. + explicit Gabow(WordGraph const& wg) { init(wg); } + //! \brief Reinitialize a Gabow object. + //! + //! This function re-initializes a Gabow object so that it is in the same + //! state as if it had just been constructed from \p wg. + //! + //! \returns A reference to `*this`. + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \warning The Gabow object only holds a reference to the underlying + //! WordGraph \p wg, and so that object must outlive the corresponding Gabow + //! object. + //! + //! \note This function does not trigger the computation of the strongly + //! connected components. Gabow& init(WordGraph const& wg); - [[nodiscard]] size_type id_no_checks(node_type v) const { + //! \brief Get the id of a strongly connected component of a node. + //! + //! This function can be used to determine the id-number of a node in the + //! underlying graph of a Gabow instance. + //! + //! \param n the node. + //! + //! \returns The id-number of the strongly connected component of \p n. + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \warning This function does not check that its argument \p n is actually + //! a node of the underlying word graph. + //! + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). + [[nodiscard]] size_type id_no_checks(node_type n) const { run(); - return _id[v]; + return _id[n]; } - //! Returns the id-number of the strongly connected component of a node. + //! \brief Returns the id-number of the strongly connected component of a + //! node. //! - //! \param nd the node. + //! This function can be used to determine the id-number of a node in the + //! underlying graph of a Gabow instance. //! - //! \returns - //! The index of the node \p nd, a value of type scc_index_type. + //! \param n the node. + //! + //! \returns The id-number of the strongly connected component of \p n. //! - //! \throws LibsemigroupsException if \p nd is not valid. + //! \throws LibsemigroupsException if \p n is greater than or equal to + //! `word_graph().number_of_nodes()`. //! - //! \complexity - //! At most \f$O(mn)\f$ where \c m is number_of_nodes() and \c n is - //! out_degree(). + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). // Not noexcept because validate_node isn't - [[nodiscard]] size_type id(node_type v) const { + [[nodiscard]] size_type id(node_type n) const { run(); - validate_node(v); - return _id[v]; + validate_node(n); + return id_no_checks(n); } - //! Returns an iterator pointing to the vector of nodes in the first scc. + //! \brief Returns a const reference to a vector of vectors containing the + //! strongly connected components. //! - //! \returns - //! A \ref const_iterator_sccs. + //! This function returns a const reference to a vector of vectors + //! containing all of the strongly connected components of the WordGraph + //! (\ref word_graph) used to construct the Gabow instance. //! - //! \throws LibsemigroupsException if it is not the case that every node - //! has exactly out_degree() out-targets. In other words, if - //! target() is libsemigroups::UNDEFINED for any node \c nd and - //! any label \c lbl. - //! \basic_guarantee + //! \returns + //! A vector of vectors of node_type. //! - //! \complexity - //! At most \f$O(mn)\f$ where \c m is number_of_nodes() and \c n is - //! out_degree(). + //! \exceptions + //! \no_libsemigroups_except //! - //! \par Parameters - //! (None) - [[nodiscard]] auto components() const { + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). + [[nodiscard]] std::vector> const& + components() const { run(); return _comps; } - [[nodiscard]] std::vector component(size_type i) const { + //! \brief Returns a const reference to a vector containing the strongly + //! connected component with given index. + //! + //! This function returns a const reference to a vector + //! containing the strongly connected components with index \p i of the + //! WordGraph (\ref word_graph) used to construct the Gabow instance. + //! + //! \param i the index of a strongly connected component. + //! + //! \returns A vector of node_type. + //! + //! \throws LibsemigroupsException if \p i is greater than or equal + //! to \ref number_of_components. + //! + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). + //! + //! \sa \ref component_of to obtain the component of a node. + [[nodiscard]] std::vector const& component(size_type i) const { run(); validate_scc_index(i); return _comps[i]; } + //! \brief Returns a const reference to a vector containing the strongly + //! connected component with given index. + //! + //! This function returns a const reference to a vector + //! containing the strongly connected components with index \p i of the + //! WordGraph (\ref word_graph) used to construct the Gabow instance. + //! + //! \param i the index of a strongly connected component. + //! + //! \returns A vector of node_type. + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \warning This function does not check that its argument \p i. + //! + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). + //! + //! \sa \ref component_of_no_checks to obtain the strongly connected + //! component of a node. [[nodiscard]] std::vector component_no_checks(size_type i) const { run(); return _comps[i]; } - //! Returns the number of strongly connected components. + //! \brief Returns the number of strongly connected components. + //! + //! This function returns the number of strongly connected components of the + //! underlying WordGraph (returned by \ref word_graph). //! //! \returns //! A `size_t`. //! - //! \throws LibsemigroupsException if it is not the case that every node - //! has exactly out_degree() out-targets. In other words, if - //! target() is libsemigroups::UNDEFINED for any node \c nd and - //! any label \c lbl. - //! \basic_guarantee - //! - //! \complexity - //! At most \f$O(mn)\f$ where \c m is number_of_nodes() and \c n is - //! out_degree(). + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). //! - //! \par Parameters - //! (None) + //! \exceptions + //! \no_libsemigroups_except [[nodiscard]] size_t number_of_components() const { run(); return _comps.size(); } - //! Returns an iterator pointing to the root of the first scc. + //! \brief Returns a range object consisting of roots of the strongly + //! connected components. //! - //! \returns - //! A \ref const_iterator_scc_roots. + //! This function returns a range object consisting of roots of the strongly + //! connected components. //! - //! \throws LibsemigroupsException if it is not the case that every node - //! has exactly out_degree() out-targets. In other words, if - //! target() is libsemigroups::UNDEFINED for any node \c nd and - //! any label \c lbl. \basic_guarantee + //! \returns + //! A range object by value. //! - //! \complexity - //! At most \f$O(mn)\f$ where \c m is number_of_nodes() and \c n is - //! out_degree(). + //! \exceptions + //! \no_libsemigroups_except //! - //! \par Parameters - //! (None) + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). + // TODO(0) add reference to range doc when available [[nodiscard]] auto roots() const { run(); return (rx::iterator_range(_comps.cbegin(), _comps.cend()) | rx::transform([](auto const& comp) { return comp[0]; })); } - //! Returns the root of a strongly connected components containing a given - //! node. + //! \brief Returns the root of the strongly connected component containing a + //! given node. //! - //! \param nd a node. + //! This function returns the root of the strongly connected component + //! containing the node \p n of the underlying WordGraph. Two nodes \c a and + //! \c b belong to the same strongly connected component if and only if + //! `root_of(a) == root_of(b)`. //! - //! \returns - //! The root of the scc containing the node \p nd, a value of - //! \ref node_type. - //! - //! \throws LibsemigroupsException if it is not the case that every node - //! has exactly out_degree() out-targets. In other words, if - //! target() is libsemigroups::UNDEFINED for any node \c nd and - //! any label \c lbl. - //! \basic_guarantee - //! - //! \complexity - //! At most \f$O(mn)\f$ where \c m is number_of_nodes() and \c n is - //! out_degree(). + //! \param n the node. + //! + //! \returns The root of the strongly connected component containing the + //! node \p n, a value of \ref WordGraph::node_type. + //! + //! \throws LibsemigroupsException if \p n is greater than or equal to + //! WordGraph::number_of_nodes of the underlying word graph. + //! + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). // Not noexcept because scc_id isn't [[nodiscard]] node_type root_of(node_type n) const { return component_of(n)[0]; } + //! \brief Returns the root of the strongly connected component containing a + //! given node. + //! + //! This function returns the root of the strongly connected component + //! containing the node \p n of the underlying WordGraph. Two nodes \c a and + //! \c b belong to the same strongly connected component if and only if + //! `root_of_no_checks(a) == root_of_no_checks(b)`. + //! + //! \param n the node. + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \returns + //! The root of the strongly connected component containing the node \p n, + //! a value of \ref WordGraph::node_type. + //! + //! \warning This function does not check that its argument \p n. + //! + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). [[nodiscard]] node_type root_of_no_checks(node_type n) const { return component_of_no_checks(n)[0]; } - //! Returns an iterator pointing to the first node in the scc with - //! the specified id-number. + //! \brief Returns a const reference to a vector containing the strongly + //! connected component of a given node. //! - //! \param i the id-number of the scc. + //! This function returns a const reference to a vector + //! containing the strongly connected components of the node \p n of the + //! WordGraph (returned by \ref word_graph) used to construct the Gabow + //! instance. //! - //! \returns - //! A \ref const_iterator_scc. + //! \param n the node. //! - //! \throws LibsemigroupsException if it is not the case that every node - //! has exactly out_degree() out-targets. In other words, if - //! target() is libsemigroups::UNDEFINED for any node \c nd and - //! any label \c lbl. + //! \returns A vector of node_type. //! - //! \throws LibsemigroupsException if \p i is not in the range \c 0 to \c - //! number_of_scc() - 1. + //! \throws LibsemigroupsException if \p n is greater than or equal + //! to `word_graph().number_of_nodes()`. //! - //! \complexity - //! At most \f$O(mn)\f$ where \c m is number_of_nodes() and \c n is - //! out_degree(). - //! - //! \note - //! \basic_guarantee - //! - [[nodiscard]] std::vector component_of(node_type n) const { + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). + [[nodiscard]] std::vector const& + component_of(node_type n) const { run(); validate_node(n); return _comps[_id[n]]; } - [[nodiscard]] std::vector + //! \brief Returns a const reference to a vector containing the strongly + //! connected component of a given node. + //! + //! This function returns a const reference to a vector + //! containing the strongly connected components of the node \p n of the + //! WordGraph (returned by \ref word_graph) used to construct the Gabow + //! instance. + //! + //! \param n the node. + //! + //! \exceptions + //! \no_libsemigroups_except + //! + //! \returns A vector of node_type. + //! + //! \warning This function does not check that its argument \p n. + //! + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). + [[nodiscard]] std::vector const& component_of_no_checks(node_type n) const { run(); return _comps[_id[n]]; } - Gabow const& reset() const noexcept; - - //! Returns a spanning forest of the strongly connected components. + //! \brief Returns a spanning forest of the strongly connected components. //! - //! Returns a Forest comprised of spanning trees for each - //! scc of \c this, rooted on the minimum node of that component, with - //! edges oriented away from the root. + //! This function returns a Forest comprised of spanning trees for each + //! strongly connected component of a Gabow object, rooted on the minimum + //! node of that component, with edges oriented away from the root. //! //! \returns //! A const reference to a Forest. //! - //! \throws LibsemigroupsException if it is not the case that every node - //! has exactly out_degree() out-targets. In other words, if - //! target() is libsemigroups::UNDEFINED for any node \c nd and - //! any label \c lbl. \basic_guarantee - //! - //! \complexity - //! At most \f$O(mn)\f$ where \c m is number_of_nodes() and \c n is - //! out_degree(). + //! \exceptions + //! \no_libsemigroups_except //! - //! \par Parameters - //! (None) + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). Forest const& spanning_forest() const; - //! Returns a reverse spanning forest of the strongly connected components. + //! \brief Returns a reverse spanning forest of the strongly connected + //! components (if they are not already known). //! - //! Returns a Forest comprised of spanning trees for each - //! scc of \c this, rooted on the minimum node of that component, with - //! edges oriented towards the root. + //! This function returns a Forest comprised of spanning trees for each + //! strongly connected component of a Gabow object, rooted on the minimum + //! node of that component, with edges oriented towards the root. //! //! \returns //! A const reference to a Forest. //! - //! \throws LibsemigroupsException if it is not the case that every node - //! has exactly out_degree() out-targets. In other words, if - //! target() is libsemigroups::UNDEFINED for any node \c nd and - //! any label \c lbl. \basic_guarantee + //! \exceptions + //! \no_libsemigroups_except //! - //! \complexity - //! At most \f$O(mn)\f$ where \c m is number_of_nodes() and \c n is - //! out_degree(). - //! - //! \par Parameters - //! (None) + //! \note This function triggers the computation of the strongly connected + //! components (if they are not already known). Forest const& reverse_spanning_forest() const; - private: - bool finished() const { + //! \brief Returns a const reference to the underlying word graph. + //! + //! This function returns a const reference to the underlying word graph. + //! + //! \returns + //! A const reference to a WordGraph. + //! + //! \exceptions + //! \noexcept + //! + //! \note This function does not trigger the computation of the strongly + //! connected components. + WordGraph const& word_graph() const noexcept { + return *_graph; + } + + //! \brief Check whether the strongly connected components have been found. + //! + //! This function returns \c true if the strongly connected components of a + //! Gabow object have already been computed and \c false if not. + //! + //! \returns + //! A \c bool. + //! + //! \exceptions + //! \noexcept + [[nodiscard]] bool has_components() const noexcept { return _finished; } + + private: + void reset() const noexcept; void run() const; void validate_node(node_type n) const; void validate_scc_index(size_t i) const; }; + //! \ingroup gabow_group + //! + //! \brief Deduction guide for Gabow objects. template Gabow(WordGraph const&) -> Gabow; + //! \ingroup gabow_group + //! + //! \brief Return a human readable representation of a Gabow object. + //! + //! Return a human readable representation of a Gabow object. + //! + //! \tparam Node the type of the nodes in the underlying WordGraph + //! + //! \param g the Gabow object. + //! + //! \exceptions + //! \no_libsemigroups_except + template + std::string to_human_readable_repr(Gabow const& g); + } // namespace libsemigroups #include "gabow.tpp" diff --git a/include/libsemigroups/gabow.tpp b/include/libsemigroups/gabow.tpp index 271b307ac..a88a3e2b7 100644 --- a/include/libsemigroups/gabow.tpp +++ b/include/libsemigroups/gabow.tpp @@ -29,12 +29,6 @@ namespace libsemigroups { _id.clear(); _bckwd_forest.init(); _forwd_forest.init(); - reset(); - return *this; - } - - template - Gabow const& Gabow::reset() const noexcept { _finished = false; _bckwd_forest_defined = false; _forwd_forest_defined = false; @@ -127,7 +121,7 @@ namespace libsemigroups { template void Gabow::run() const { - if (finished()) { + if (has_components()) { return; } @@ -222,4 +216,20 @@ namespace libsemigroups { i); } } + + template + std::string to_human_readable_repr(Gabow const& g) { + std::string suffix = ""; + if (g.has_components()) { + suffix = fmt::format("{} component{}", + g.number_of_components(), + g.number_of_components() != 1 ? "s" : ""); + } else { + suffix = "components not yet found"; + } + return fmt::format("", + g.word_graph().number_of_nodes(), + g.word_graph().number_of_nodes() != 1 ? "s" : "", + suffix); + } } // namespace libsemigroups diff --git a/tests/test-gabow.cpp b/tests/test-gabow.cpp index 8ff4c8f9e..801b9103f 100644 --- a/tests/test-gabow.cpp +++ b/tests/test-gabow.cpp @@ -61,7 +61,7 @@ namespace libsemigroups { Gabow scc(wg); for (size_t j = 1; j < 100; ++j) { wg.add_nodes(j); - scc.reset(); + scc.init(wg); for (size_t i = 0; i < j * (j + 1) / 2; ++i) { REQUIRE(scc.id(i) == i); @@ -75,7 +75,7 @@ namespace libsemigroups { Gabow scc(wg); for (size_t j = 2; j < 50; ++j) { word_graph::add_cycle(wg, j); - scc.reset(); + scc.init(wg); REQUIRE((wg.nodes() | filter([&scc, j](auto v) { return scc.id(v) == j - 2; }) | count()) @@ -247,7 +247,7 @@ namespace libsemigroups { (wg.nodes() | all_of([&scc](node_type i) { return scc.id(i) == 0; }))); word_graph::add_cycle(wg, 10101); - scc.reset(); + scc.init(wg); REQUIRE((wg.nodes() | take(100000) | all_of([&scc](node_type i) { return scc.id(i) == 0; }))); REQUIRE((wg.nodes() | skip_n(100000) @@ -269,7 +269,7 @@ namespace libsemigroups { REQUIRE(wg.number_of_nodes() == 2 * n); REQUIRE(wg.number_of_edges() == 2 * n * n); - scc.reset(); + scc.init(wg); REQUIRE(scc.number_of_components() == 2); auto expected = std::vector(n, 0); @@ -340,4 +340,18 @@ namespace libsemigroups { REQUIRE(scc.reverse_spanning_forest() == to_forest({4, 2, 0, 4, UNDEFINED}, {2, 0, 1, 0, UNDEFINED})); } + + LIBSEMIGROUPS_TEST_CASE("Gabow", + "013", + "to_human_readable_repr", + "[quick][gabow]") { + auto wg = to_word_graph( + 5, {{0, 1, 4, 3}, {2}, {2, 0, 3, 3}, {4, 1}, {1, 0, 2}}); + Gabow scc(wg); + REQUIRE(to_human_readable_repr(scc) + == ""); + REQUIRE(scc.number_of_components() == 1); + REQUIRE(to_human_readable_repr(scc) + == ""); + } } // namespace libsemigroups