// libsemigroups - C++ library for semigroups and monoids
// Copyright (C) 2020 James D. Mitchell, Maria Tsalakou
//
// 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 all_of
#include <cstddef>
// for size_t
#include <cstdint>
// for uint8_t
#include <iterator>
// for reverse_iterator, operat...
#include <stdexcept>
// for runtime_error
#include <string>
// for basic_string, operator==
#include <utility>
// for pair
#include <vector>
// for vector, operator==
#include "catch.hpp" // for REQUIRE, REQUIRE_NOTHROW, REQUIRE_THROWS_AS
#include "test-main.hpp" // for LIBSEMIGROUPS_TEST_CASE
#include "libsemigroups/digraph-helper.hpp" // for is_acyclic, topological_...
#include "libsemigroups/digraph.hpp" // for ActionDigraph, operator<<
#include "libsemigroups/string.hpp" // for to_string
#include "libsemigroups/types.hpp" // for word_type
namespace libsemigroups {
namespace {
void add_path(ActionDigraph<size_t>& digraph, size_t n) {
size_t old_nodes = digraph.number_of_nodes();
digraph.add_nodes(n);
for (size_t i = old_nodes; i < digraph.number_of_nodes() - 1; ++i) {
digraph.add_edge(i, i + 1, 0);
}
}
ActionDigraph<size_t> path(size_t n) {
ActionDigraph<size_t> g(0, 1);
add_path(g, n);
return g;
}
void add_clique(ActionDigraph<size_t>& digraph, size_t n) {
if (n != digraph.out_degree()) {
throw std::runtime_error(
"can't do it!");
}
size_t old_nodes = digraph.number_of_nodes();
digraph.add_nodes(n);
for (size_t i = old_nodes; i < digraph.number_of_nodes(); ++i) {
for (size_t j = old_nodes; j < digraph.number_of_nodes(); ++j) {
digraph.add_edge(i, j, j - old_nodes);
}
}
}
ActionDigraph<size_t> clique(size_t n) {
ActionDigraph<size_t> g(0, n);
add_clique(g, n);
return g;
}
}
// namespace
LIBSEMIGROUPS_TEST_CASE(
"is_acyclic",
"000",
"2-cycle",
"[quick]") {
ActionDigraph<size_t> ad;
ad.add_nodes(2);
ad.add_to_out_degree(1);
ad.add_edge(0, 1, 0);
ad.add_edge(1, 0, 0);
REQUIRE(!action_digraph_helper::is_acyclic(ad));
REQUIRE(action_digraph_helper::topological_sort(ad).empty());
}
LIBSEMIGROUPS_TEST_CASE(
"is_acyclic",
"001",
"1-cycle",
"[quick]") {
ActionDigraph<size_t> ad;
ad.add_nodes(1);
ad.add_to_out_degree(1);
ad.add_edge(0, 0, 0);
REQUIRE(!action_digraph_helper::is_acyclic(ad));
REQUIRE(action_digraph_helper::topological_sort(ad).empty());
}
LIBSEMIGROUPS_TEST_CASE(
"is_acyclic",
"002",
"multi-digraph",
"[quick]") {
using node_type = ActionDigraph<size_t>::node_type;
ActionDigraph<size_t> ad;
ad.add_nodes(2);
ad.add_to_out_degree(2);
ad.add_edge(0, 1, 0);
ad.add_edge(0, 1, 1);
REQUIRE(action_digraph_helper::is_acyclic(ad));
REQUIRE(action_digraph_helper::topological_sort(ad)
== std::vector<node_type>({1, 0}));
}
LIBSEMIGROUPS_TEST_CASE(
"is_acyclic",
"003",
"complete digraph 100",
"[quick]") {
ActionDigraph<size_t> ad;
size_t
const n = 100;
ad.add_nodes(n);
ad.add_to_out_degree(n);
for (size_t i = 0; i < n; ++i) {
for (size_t j = 0; j < n; ++j) {
if (i != j) {
ad.add_edge(i, j, j);
}
}
}
REQUIRE(!action_digraph_helper::is_acyclic(ad));
REQUIRE(action_digraph_helper::topological_sort(ad).empty());
}
LIBSEMIGROUPS_TEST_CASE(
"is_acyclic",
"004",
"acyclic digraph with 20000 nodes",
"[quick]") {
ActionDigraph<size_t> ad;
size_t
const n = 20000;
ad.add_nodes(n);
ad.add_to_out_degree(2);
for (size_t i = 0; i < (n / 2 - 1); ++i) {
ad.add_edge(i, i + 1, 0);
}
ad.add_edge(n / 2 - 1, n - 1, 1);
ad.add_edge(n / 2 + 1, (3 * n) / 4 - 1, 1);
ad.add_edge(n / 2, 0, 1);
for (size_t i = n / 2; i < n - 1; ++i) {
ad.add_edge(i, i + 1, 0);
}
REQUIRE(action_digraph_helper::is_acyclic(ad));
REQUIRE(action_digraph_helper::topological_sort(ad).size()
== ad.number_of_nodes());
}
LIBSEMIGROUPS_TEST_CASE(
"is_acyclic",
"005",
"acyclic digraph with 10 million nodes",
"[standard]") {
ActionDigraph<size_t> ad;
size_t
const n = 10000000;
ad.add_nodes(n);
ad.add_to_out_degree(2);
for (size_t i = 0; i < (n / 2 - 1); ++i) {
ad.add_edge(i, i + 1, 0);
}
ad.add_edge(n / 2 - 1, n - 1, 1);
ad.add_edge(n / 2 + 1, (3 * n) / 4 - 1, 1);
ad.add_edge(n / 2, 0, 1);
for (size_t i = n / 2; i < n - 1; ++i) {
ad.add_edge(i, i + 1, 0);
}
REQUIRE(action_digraph_helper::is_acyclic(ad));
REQUIRE(action_digraph_helper::topological_sort(ad).size() == n);
}
LIBSEMIGROUPS_TEST_CASE(
"is_acyclic",
"006",
"for a node",
"[quick]") {
using node_type = ActionDigraph<size_t>::node_type;
ActionDigraph<size_t> ad;
size_t
const n = 100;
ad.add_nodes(n);
ad.add_to_out_degree(2);
for (size_t i = 0; i < n - 1; ++i) {
ad.add_edge(i, i + 1, i % 2);
}
action_digraph_helper::add_cycle(ad, 100);
REQUIRE(std::all_of(
ad.cbegin_nodes(), ad.cbegin_nodes() + 100, [&ad](node_type
const& v) {
return action_digraph_helper::is_acyclic(ad, v);
}));
REQUIRE(std::none_of(ad.crbegin_nodes(),
ad.crbegin_nodes() + 100,
[&ad](node_type
const& v) {
return action_digraph_helper::is_acyclic(ad, v);
}));
REQUIRE(!action_digraph_helper::is_acyclic(ad));
REQUIRE(action_digraph_helper::is_acyclic(ad, 10, 20));
REQUIRE(!action_digraph_helper::is_acyclic(ad, 100, 120));
REQUIRE(action_digraph_helper::is_acyclic(ad, 10, 120));
}
LIBSEMIGROUPS_TEST_CASE(
"is_acyclic",
"007",
"for a node",
"[quick]") {
ActionDigraph<size_t> ad;
using node_type = decltype(ad)::node_type;
ad.add_nodes(4);
ad.add_to_out_degree(1);
ad.add_edge(0, 1, 0);
ad.add_edge(1, 0, 0);
ad.add_edge(2, 3, 0);
REQUIRE(!action_digraph_helper::is_acyclic(ad));
REQUIRE(action_digraph_helper::topological_sort(ad).empty());
REQUIRE(!action_digraph_helper::is_acyclic(ad, 0));
REQUIRE(action_digraph_helper::topological_sort(ad, 0).empty());
REQUIRE(!action_digraph_helper::is_acyclic(ad, 1));
REQUIRE(action_digraph_helper::topological_sort(ad, 1).empty());
REQUIRE(action_digraph_helper::is_acyclic(ad, 2));
REQUIRE(action_digraph_helper::topological_sort(ad, 2)
== std::vector<node_type>({3, 2}));
REQUIRE(action_digraph_helper::is_acyclic(ad, 3));
REQUIRE(action_digraph_helper::topological_sort(ad, 3)
== std::vector<node_type>({3}));
}
LIBSEMIGROUPS_TEST_CASE(
"is_reachable",
"008",
"acyclic 20 node digraph",
"[quick]") {
ActionDigraph<size_t> ad;
size_t
const n = 20;
ad.add_nodes(n);
ad.add_to_out_degree(2);
for (size_t i = 0; i < (n / 2 - 1); ++i) {
ad.add_edge(i, i + 1, 0);
}
ad.add_edge(n / 2 - 1, n - 1, 1);
ad.add_edge(n / 2 + 1, (3 * n) / 4 - 1, 1);
ad.add_edge(n / 2, 0, 1);
for (size_t i = n / 2; i < n - 1; ++i) {
ad.add_edge(i, i + 1, 0);
}
REQUIRE(!action_digraph_helper::is_reachable(ad, 1, 10));
REQUIRE(action_digraph_helper::is_reachable(ad, 10, 1));
REQUIRE_THROWS_AS(action_digraph_helper::is_reachable(ad, 20, 1),
LibsemigroupsException);
REQUIRE_THROWS_AS(action_digraph_helper::is_reachable(ad, 1, 20),
LibsemigroupsException);
REQUIRE(action_digraph_helper::is_reachable(ad, 1, 1));
}
LIBSEMIGROUPS_TEST_CASE(
"is_reachable",
"009",
"100 node path",
"[quick]") {
ActionDigraph<size_t> ad;
size_t
const n = 100;
ad.add_nodes(n);
ad.add_to_out_degree(2);
for (size_t i = 0; i < n - 1; ++i) {
ad.add_edge(i, i + 1, i % 2);
}
for (
auto it1 = ad.cbegin_nodes(); it1 < ad.cend_nodes(); ++it1) {
for (
auto it2 = it1 + 1; it2 < ad.cend_nodes(); ++it2) {
REQUIRE(action_digraph_helper::is_reachable(ad, *it1, *it2));
REQUIRE(!action_digraph_helper::is_reachable(ad, *it2, *it1));
}
}
}
LIBSEMIGROUPS_TEST_CASE(
"is_reachable",
"010",
"100 node cycle",
"[quick]") {
ActionDigraph<size_t> ad;
ad.add_to_out_degree(1);
action_digraph_helper::add_cycle(ad, 100);
for (
auto it1 = ad.cbegin_nodes(); it1 < ad.cend_nodes(); ++it1) {
for (
auto it2 = it1 + 1; it2 < ad.cend_nodes(); ++it2) {
REQUIRE(action_digraph_helper::is_reachable(ad, *it1, *it2));
REQUIRE(action_digraph_helper::is_reachable(ad, *it2, *it1));
}
}
}
LIBSEMIGROUPS_TEST_CASE(
"is_reachable",
"011",
"20 node clique",
"[quick]") {
ActionDigraph<size_t> ad = clique(20);
for (
auto it1 = ad.cbegin_nodes(); it1 < ad.cend_nodes(); ++it1) {
for (
auto it2 = it1 + 1; it2 < ad.cend_nodes(); ++it2) {
REQUIRE(action_digraph_helper::is_reachable(ad, *it1, *it2));
REQUIRE(action_digraph_helper::is_reachable(ad, *it2, *it1));
}
}
REQUIRE(ad.validate());
REQUIRE(action_digraph_helper::topological_sort(ad).empty());
REQUIRE(action_digraph_helper::topological_sort(ad, 0).empty());
}
LIBSEMIGROUPS_TEST_CASE(
"follow_path",
"012",
"20 node path",
"[quick]") {
ActionDigraph<size_t> ad = path(20);
for (
auto it = ad.cbegin_panilo(0); it != ad.cend_panilo(); ++it) {
REQUIRE(action_digraph_helper::follow_path(ad, 0, it->first)
== it->second);
REQUIRE(action_digraph_helper::follow_path_nc(ad, 0, it->first)
== it->second);
}
}
LIBSEMIGROUPS_TEST_CASE(
"validate_label",
"013",
"20 node path",
"[quick]") {
ActionDigraph<size_t> ad = path(20);
REQUIRE_THROWS_AS(action_digraph_helper::validate_label(ad, 10),
LibsemigroupsException);
}
LIBSEMIGROUPS_TEST_CASE(
"last_node_on_path_nc",
"014",
"20 node path",
"[quick]") {
ActionDigraph<size_t> ad = path(20);
word_type path = {};
for (size_t i = 0; i < 19; ++i) {
path.push_back(0);
REQUIRE(action_digraph_helper::last_node_on_path_nc(
ad, 0, path.cbegin(), path.cend())
.first
== i + 1);
}
path.push_back(0);
auto p = action_digraph_helper::last_node_on_path_nc(
ad, 0, path.cbegin(), path.cend());
REQUIRE(p.first == 19);
REQUIRE(p.second == path.cend() - 1);
}
LIBSEMIGROUPS_TEST_CASE(
"action_digraph_helper",
"015",
"detail::to_string",
"[quick]") {
ActionDigraph<size_t> ad = path(6);
REQUIRE(detail::to_string(ad) ==
"{{1}, {2}, {3}, {4}, {5}, {-}}");
REQUIRE(action_digraph_helper::detail::to_string(ad)
== R
"V0G0N(ActionDigraph ad;
ad.add_nodes(6);
ad.add_to_out_degree(1);
ad.add_edge(0, 1, 0);
ad.add_edge(1, 2, 0);
ad.add_edge(2, 3, 0);
ad.add_edge(3, 4, 0);
ad.add_edge(4, 5, 0);
)V0G0N
");
}
LIBSEMIGROUPS_TEST_CASE(
"action_digraph_helper",
"016",
"make",
"[quick]") {
auto ad = action_digraph_helper::make<uint8_t>(
5, {{0, 0}, {1, 1}, {2}, {3, 3}});
REQUIRE(detail::to_string(ad)
==
"{{0, 0}, {1, 1}, {2, -}, {3, 3}, {-, -}}");
REQUIRE_THROWS_AS(action_digraph_helper::make<uint8_t>(
5, {{0, 0}, {1, 1, 1}, {2}, {3, 3}}),
LibsemigroupsException);
ad = ActionDigraph<uint8_t>(5, 2);
REQUIRE(detail::to_string(ad)
==
"{{-, -}, {-, -}, {-, -}, {-, -}, {-, -}}");
}
LIBSEMIGROUPS_TEST_CASE(
"action_digraph_helper",
"017",
"is_connected",
"[quick]") {
auto ad
= action_digraph_helper::make<size_t>(5, {{0, 0}, {1, 1}, {2}, {3, 3}});
REQUIRE(!action_digraph_helper::is_connected(ad));
ad = path(1
'000);
REQUIRE(action_digraph_helper::is_connected(ad));
REQUIRE(ad.number_of_nodes() == 1
'000);
action_digraph_helper::add_cycle(ad, 100);
REQUIRE(ad.number_of_nodes() == 1
'100);
REQUIRE(!action_digraph_helper::is_connected(ad));
ad.add_to_out_degree(1);
ad.add_edge(0, 1
'000, 1);
REQUIRE(action_digraph_helper::is_connected(ad));
ad = ActionDigraph<size_t>();
REQUIRE(action_digraph_helper::is_connected(ad));
}
LIBSEMIGROUPS_TEST_CASE(
"action_digraph_helper",
"018",
"is_strictly_cyclic",
"[quick]") {
auto ad
= action_digraph_helper::make<size_t>(5, {{0, 0}, {1, 1}, {2}, {3, 3}});
REQUIRE(!action_digraph_helper::is_strictly_cyclic(ad));
ad = path(1
'000);
REQUIRE(action_digraph_helper::is_strictly_cyclic(ad));
REQUIRE(ad.number_of_nodes() == 1
'000);
action_digraph_helper::add_cycle(ad, 100);
REQUIRE(ad.number_of_nodes() == 1
'100);
REQUIRE(!action_digraph_helper::is_strictly_cyclic(ad));
ad.add_to_out_degree(1);
ad.add_edge(0, 1
'000, 1);
REQUIRE(action_digraph_helper::is_strictly_cyclic(ad));
ad = ActionDigraph<size_t>();
REQUIRE(action_digraph_helper::is_strictly_cyclic(ad));
}
}
// namespace libsemigroups