Files
ortools-clone/ortools/graph/ebert_graph_test.cc

1122 lines
34 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/graph/ebert_graph.h"
#include <cstdint>
#include <random>
#include <string>
#include "absl/random/distributions.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "benchmark/benchmark.h"
#include "gtest/gtest.h"
#include "ortools/util/permutation.h"
namespace operations_research {
template <typename GraphType>
std::string Stringify(const GraphType& graph,
typename GraphType::ArcIndex arc) {
return absl::StrCat(" Arc ", arc, ": ", graph.Tail(arc), " -> ",
graph.Head(arc), "\n");
}
template <typename GraphType>
std::string Stringify(const GraphType& graph,
typename GraphType::NodeIndex tail,
typename GraphType::ArcIndex arc) {
return absl::StrCat(" Arc ", arc, ": ", tail, " -> ", graph.Head(arc),
"\n");
}
// We have to base the TestEbertGraph utility function template on a
// class/struct template because partial specialization is not allowed for
// function templates, and we use partial specialization to turn off reverse-arc
// testing for graph representations that don't have reverse arcs.
//
// First, we have the complete case for graphs that represent their reverse arcs
// and therefore can be more thoroughly tested. The constructor does all the
// testing work.
template <typename GraphType, bool has_reverse_arcs, bool is_dynamic>
struct TestEbertGraphRunner {
TestEbertGraphRunner(
const GraphType& graph, absl::string_view expected_graph_arc_list,
absl::string_view expected_adjacency_list,
absl::string_view expected_incoming_arc_list,
absl::string_view expected_outgoing_arc_list,
absl::string_view expected_debug_string,
absl::string_view unused_expected_forward_debug_string,
absl::string_view unused_expected_forward_static_debug_string) {
std::string graph_arc_list = "";
for (typename GraphType::ArcIterator arc_it(graph); arc_it.Ok();
arc_it.Next()) {
typename GraphType::ArcIndex arc = arc_it.Index();
absl::StrAppend(&graph_arc_list, Stringify(graph, arc));
EXPECT_EQ(graph.DirectArc(arc), graph.Opposite(graph.ReverseArc(arc)));
}
EXPECT_EQ(expected_graph_arc_list, graph_arc_list);
std::string adjacency_list = "";
for (typename GraphType::NodeIterator node_it(graph); node_it.Ok();
node_it.Next()) {
typename GraphType::NodeIndex node = node_it.Index();
absl::StrAppend(&adjacency_list, " Node ", node, ":\n");
for (typename GraphType::OutgoingOrOppositeIncomingArcIterator arc_it(
graph, node);
arc_it.Ok(); arc_it.Next()) {
typename GraphType::ArcIndex arc = arc_it.Index();
EXPECT_TRUE(graph.IsOutgoingOrOppositeIncoming(arc, node));
absl::StrAppend(&adjacency_list, Stringify(graph, arc));
EXPECT_EQ(node, graph.Tail(arc));
}
}
EXPECT_EQ(expected_adjacency_list, adjacency_list);
std::string incoming_arc_list = "";
for (typename GraphType::NodeIterator node_it(graph); node_it.Ok();
node_it.Next()) {
typename GraphType::NodeIndex node = node_it.Index();
absl::StrAppend(&incoming_arc_list, " Node ", node, ":\n");
for (typename GraphType::IncomingArcIterator arc_it(graph, node);
arc_it.Ok(); arc_it.Next()) {
typename GraphType::ArcIndex arc = arc_it.Index();
EXPECT_TRUE(graph.IsIncoming(arc, node));
// We assume there are no self-loops in the graph.
EXPECT_FALSE(graph.IsOutgoing(arc, node));
absl::StrAppend(&incoming_arc_list, Stringify(graph, arc));
EXPECT_FALSE(graph.IsReverse(arc));
EXPECT_EQ(node, graph.Head(arc));
}
}
EXPECT_EQ(expected_incoming_arc_list, incoming_arc_list);
std::string outgoing_arc_list = "";
for (typename GraphType::NodeIterator node_it(graph); node_it.Ok();
node_it.Next()) {
typename GraphType::NodeIndex node = node_it.Index();
absl::StrAppend(&outgoing_arc_list, " Node ", node, ":\n");
for (typename GraphType::OutgoingArcIterator arc_it(graph, node);
arc_it.Ok(); arc_it.Next()) {
typename GraphType::ArcIndex arc = arc_it.Index();
// We assume there are no self-loops in the graph.
EXPECT_FALSE(graph.IsIncoming(arc, node));
EXPECT_TRUE(graph.IsOutgoing(arc, node));
absl::StrAppend(&outgoing_arc_list, Stringify(graph, arc));
EXPECT_TRUE(graph.IsDirect(arc));
EXPECT_EQ(node, graph.Tail(arc));
EXPECT_EQ(node, graph.DirectArcTail(arc));
}
}
EXPECT_EQ(expected_outgoing_arc_list, outgoing_arc_list);
EXPECT_EQ(expected_debug_string, graph.DebugString());
}
};
// Case for graphs that don't have their reverse arcs and therefore cannot be
// tested as completely as if they did, but that are dynamic and so can be
// tested somewhat more than if they weren't. Again, the constructor does all
// the testing work.
template <typename GraphType>
struct TestEbertGraphRunner<GraphType, false, true> {
TestEbertGraphRunner(
const GraphType& graph, absl::string_view unused_expected_graph_arc_list,
absl::string_view unused_expected_adjacency_list,
absl::string_view unused_expected_incoming_arc_list,
absl::string_view expected_outgoing_arc_list,
absl::string_view unused_expected_debug_string,
absl::string_view expected_forward_debug_string,
absl::string_view unused_expected_forward_static_debug_string) {
std::string outgoing_arc_list = "";
for (typename GraphType::NodeIterator node_it(graph); node_it.Ok();
node_it.Next()) {
typename GraphType::NodeIndex node = node_it.Index();
absl::StrAppend(&outgoing_arc_list, " Node ", node, ":\n");
for (typename GraphType::OutgoingArcIterator arc_it(graph, node);
arc_it.Ok(); arc_it.Next()) {
typename GraphType::ArcIndex arc = arc_it.Index();
// We assume no self-loops in the graph.
EXPECT_FALSE(graph.IsIncoming(arc, node));
absl::StrAppend(&outgoing_arc_list, Stringify(graph, node, arc));
}
}
EXPECT_EQ(expected_outgoing_arc_list, outgoing_arc_list);
EXPECT_EQ(expected_forward_debug_string, graph.DebugString());
}
};
// Case for graphs that don't have their reverse arcs and are static. Due to
// these restrictions, there is less to check about these graphs than for the
// more complicated kinds of graphs.
template <typename GraphType>
struct TestEbertGraphRunner<GraphType, false, false> {
TestEbertGraphRunner(const GraphType& graph,
absl::string_view unused_expected_graph_arc_list,
absl::string_view unused_expected_adjacency_list,
absl::string_view unused_expected_incoming_arc_list,
absl::string_view unused_expected_outgoing_arc_list,
absl::string_view unused_expected_debug_string,
absl::string_view unused_expected_forward_debug_string,
absl::string_view expected_forward_static_debug_string) {
EXPECT_EQ(expected_forward_static_debug_string, graph.DebugString());
}
};
// Tests that various string representations of the given graph match
// the given strings.
template <typename GraphType>
void TestEbertGraph(const GraphType& graph,
absl::string_view expected_graph_arc_list,
absl::string_view expected_adjacency_list,
absl::string_view expected_incoming_arc_list,
absl::string_view expected_outgoing_arc_list,
absl::string_view expected_debug_string,
absl::string_view expected_forward_debug_string,
absl::string_view expected_forward_static_debug_string) {
TestEbertGraphRunner<GraphType, graph_traits<GraphType>::has_reverse_arcs,
graph_traits<GraphType>::is_dynamic>
test_object(graph, expected_graph_arc_list, expected_adjacency_list,
expected_incoming_arc_list, expected_outgoing_arc_list,
expected_debug_string, expected_forward_debug_string,
expected_forward_static_debug_string);
}
template <typename GraphType>
class DebugStringEbertGraphTest : public ::testing::Test {};
typedef ::testing::Types<EbertGraph<int16_t, int16_t> >
EbertGraphTypesForDebugStringTesting;
TYPED_TEST_SUITE(DebugStringEbertGraphTest,
EbertGraphTypesForDebugStringTesting);
TYPED_TEST(DebugStringEbertGraphTest, Test1) {
TypeParam graph(4, 6);
graph.AddArc(0, 1);
graph.AddArc(0, 2);
graph.AddArc(1, 3);
graph.AddArc(2, 3);
graph.AddArc(2, 1);
graph.AddArc(1, 2);
const std::string kGraphArcList =
" Arc 0: 0 -> 1\n"
" Arc 1: 0 -> 2\n"
" Arc 2: 1 -> 3\n"
" Arc 3: 2 -> 3\n"
" Arc 4: 2 -> 1\n"
" Arc 5: 1 -> 2\n";
const std::string kExpectedAdjacencyList =
" Node 0:\n"
" Arc 1: 0 -> 2\n"
" Arc 0: 0 -> 1\n"
" Node 1:\n"
" Arc 5: 1 -> 2\n"
" Arc -5: 1 -> 2\n"
" Arc 2: 1 -> 3\n"
" Arc -1: 1 -> 0\n"
" Node 2:\n"
" Arc -6: 2 -> 1\n"
" Arc 4: 2 -> 1\n"
" Arc 3: 2 -> 3\n"
" Arc -2: 2 -> 0\n"
" Node 3:\n"
" Arc -4: 3 -> 2\n"
" Arc -3: 3 -> 1\n";
const std::string kExpectedIncomingArcList =
" Node 0:\n"
" Node 1:\n"
" Arc 4: 2 -> 1\n"
" Arc 0: 0 -> 1\n"
" Node 2:\n"
" Arc 5: 1 -> 2\n"
" Arc 1: 0 -> 2\n"
" Node 3:\n"
" Arc 3: 2 -> 3\n"
" Arc 2: 1 -> 3\n";
const std::string kExpectedOutgoingArcList =
" Node 0:\n"
" Arc 1: 0 -> 2\n"
" Arc 0: 0 -> 1\n"
" Node 1:\n"
" Arc 5: 1 -> 2\n"
" Arc 2: 1 -> 3\n"
" Node 2:\n"
" Arc 4: 2 -> 1\n"
" Arc 3: 2 -> 3\n"
" Node 3:\n";
const std::string kExpectedDebugString =
"Arcs:(node, next arc) :\n"
" -6:(1,4)\n"
" -5:(2,2)\n"
" -4:(2,-3)\n"
" -3:(1,NilArc)\n"
" -2:(0,NilArc)\n"
" -1:(0,NilArc)\n"
" 0:(1,NilArc)\n"
" 1:(2,0)\n"
" 2:(3,-1)\n"
" 3:(3,-2)\n"
" 4:(1,3)\n"
" 5:(2,-5)\n"
"Node:First arc :\n"
" 0:1\n"
" 1:5\n"
" 2:-6\n"
" 3:-4\n";
const std::string kExpectedForwardDebugString =
"Arcs:(node, next arc) :\n"
" 0:(1,NilArc)\n"
" 1:(2,0)\n"
" 2:(3,NilArc)\n"
" 3:(3,NilArc)\n"
" 4:(1,3)\n"
" 5:(2,2)\n"
"Node:First arc :\n"
" 0:1\n"
" 1:5\n"
" 2:4\n"
" 3:NilArc\n";
TestEbertGraph(graph, kGraphArcList, kExpectedAdjacencyList,
kExpectedIncomingArcList, kExpectedOutgoingArcList,
kExpectedDebugString, kExpectedForwardDebugString, "");
}
// Unfortunately, this class template has to be defined outside the Test2 method
// where it is used, or a compiler bug gets tickled.
template <typename GraphType>
class ArcFunctorByHead {
public:
explicit ArcFunctorByHead(const GraphType& graph) : graph_(graph) {}
bool operator()(typename GraphType::ArcIndex a,
typename GraphType::ArcIndex b) const {
return ((graph_.Head(a) < graph_.Head(b)) ||
((graph_.Head(a) == graph_.Head(b)) &&
(graph_.Tail(a) < graph_.Tail(b))));
}
private:
const GraphType& graph_;
};
// Unfortunately this class template has to be defined outside the Test2 method
// where it is used, or a compiler bug gets tickled.
template <typename GraphType>
class ArcFunctorByTail {
public:
explicit ArcFunctorByTail(const GraphType& graph) : graph_(graph) {}
bool operator()(typename GraphType::ArcIndex a,
typename GraphType::ArcIndex b) const {
return ((graph_.Tail(a) < graph_.Tail(b)) ||
((graph_.Tail(a) == graph_.Tail(b)) &&
(graph_.Head(a) < graph_.Head(b))));
}
private:
const GraphType& graph_;
};
TYPED_TEST(DebugStringEbertGraphTest, Test2) {
TypeParam graph(3, 6);
graph.AddArc(0, 1);
graph.AddArc(1, 0);
graph.AddArc(1, 2);
graph.AddArc(2, 1);
graph.AddArc(0, 2);
graph.AddArc(2, 0);
const std::string kGraphArcList =
" Arc 0: 0 -> 1\n"
" Arc 1: 1 -> 0\n"
" Arc 2: 1 -> 2\n"
" Arc 3: 2 -> 1\n"
" Arc 4: 0 -> 2\n"
" Arc 5: 2 -> 0\n";
const std::string kExpectedAdjacencyList =
" Node 0:\n"
" Arc -6: 0 -> 2\n"
" Arc 4: 0 -> 2\n"
" Arc -2: 0 -> 1\n"
" Arc 0: 0 -> 1\n"
" Node 1:\n"
" Arc -4: 1 -> 2\n"
" Arc 2: 1 -> 2\n"
" Arc 1: 1 -> 0\n"
" Arc -1: 1 -> 0\n"
" Node 2:\n"
" Arc 5: 2 -> 0\n"
" Arc -5: 2 -> 0\n"
" Arc 3: 2 -> 1\n"
" Arc -3: 2 -> 1\n";
const std::string kExpectedIncomingArcList =
" Node 0:\n"
" Arc 5: 2 -> 0\n"
" Arc 1: 1 -> 0\n"
" Node 1:\n"
" Arc 3: 2 -> 1\n"
" Arc 0: 0 -> 1\n"
" Node 2:\n"
" Arc 4: 0 -> 2\n"
" Arc 2: 1 -> 2\n";
const std::string kExpectedOutgoingArcList =
" Node 0:\n"
" Arc 4: 0 -> 2\n"
" Arc 0: 0 -> 1\n"
" Node 1:\n"
" Arc 2: 1 -> 2\n"
" Arc 1: 1 -> 0\n"
" Node 2:\n"
" Arc 5: 2 -> 0\n"
" Arc 3: 2 -> 1\n";
const std::string kExpectedDebugString =
"Arcs:(node, next arc) :\n"
" -6:(2,4)\n"
" -5:(0,3)\n"
" -4:(2,2)\n"
" -3:(1,NilArc)\n"
" -2:(1,0)\n"
" -1:(0,NilArc)\n"
" 0:(1,NilArc)\n"
" 1:(0,-1)\n"
" 2:(2,1)\n"
" 3:(1,-3)\n"
" 4:(2,-2)\n"
" 5:(0,-5)\n"
"Node:First arc :\n"
" 0:-6\n"
" 1:-4\n"
" 2:5\n";
const std::string kExpectedForwardDebugString =
"Arcs:(node, next arc) :\n"
" 0:(1,NilArc)\n"
" 1:(0,NilArc)\n"
" 2:(2,1)\n"
" 3:(1,NilArc)\n"
" 4:(2,0)\n"
" 5:(0,3)\n"
"Node:First arc :\n"
" 0:4\n"
" 1:2\n"
" 2:5\n";
TestEbertGraph(graph, kGraphArcList, kExpectedAdjacencyList,
kExpectedIncomingArcList, kExpectedOutgoingArcList,
kExpectedDebugString, kExpectedForwardDebugString, "");
graph.GroupForwardArcsByFunctor(ArcFunctorByHead<TypeParam>(graph), nullptr);
const std::string kGraphHeadGroupedArcList =
" Arc 0: 1 -> 0\n"
" Arc 1: 2 -> 0\n"
" Arc 2: 0 -> 1\n"
" Arc 3: 2 -> 1\n"
" Arc 4: 0 -> 2\n"
" Arc 5: 1 -> 2\n";
const std::string kExpectedHeadGroupedAdjacencyList =
" Node 0:\n"
" Arc 4: 0 -> 2\n"
" Arc 2: 0 -> 1\n"
" Arc -2: 0 -> 2\n"
" Arc -1: 0 -> 1\n"
" Node 1:\n"
" Arc 5: 1 -> 2\n"
" Arc -4: 1 -> 2\n"
" Arc -3: 1 -> 0\n"
" Arc 0: 1 -> 0\n"
" Node 2:\n"
" Arc -6: 2 -> 1\n"
" Arc -5: 2 -> 0\n"
" Arc 3: 2 -> 1\n"
" Arc 1: 2 -> 0\n";
const std::string kExpectedHeadGroupedIncomingArcList =
" Node 0:\n"
" Arc 1: 2 -> 0\n"
" Arc 0: 1 -> 0\n"
" Node 1:\n"
" Arc 3: 2 -> 1\n"
" Arc 2: 0 -> 1\n"
" Node 2:\n"
" Arc 5: 1 -> 2\n"
" Arc 4: 0 -> 2\n";
const std::string kExpectedHeadGroupedOutgoingArcList =
" Node 0:\n"
" Arc 4: 0 -> 2\n"
" Arc 2: 0 -> 1\n"
" Node 1:\n"
" Arc 5: 1 -> 2\n"
" Arc 0: 1 -> 0\n"
" Node 2:\n"
" Arc 3: 2 -> 1\n"
" Arc 1: 2 -> 0\n";
const std::string kExpectedHeadGroupedDebugString =
"Arcs:(node, next arc) :\n"
" -6:(1,-5)\n"
" -5:(0,3)\n"
" -4:(2,-3)\n"
" -3:(0,0)\n"
" -2:(2,-1)\n"
" -1:(1,NilArc)\n"
" 0:(0,NilArc)\n"
" 1:(0,NilArc)\n"
" 2:(1,-2)\n"
" 3:(1,1)\n"
" 4:(2,2)\n"
" 5:(2,-4)\n"
"Node:First arc :\n"
" 0:4\n"
" 1:5\n"
" 2:-6\n";
const std::string kExpectedHeadGroupedForwardDebugString =
"Arcs:(node, next arc) :\n"
" 0:(0,NilArc)\n"
" 1:(0,NilArc)\n"
" 2:(1,NilArc)\n"
" 3:(1,1)\n"
" 4:(2,2)\n"
" 5:(2,0)\n"
"Node:First arc :\n"
" 0:4\n"
" 1:5\n"
" 2:3\n";
TestEbertGraph(
graph, kGraphHeadGroupedArcList, kExpectedHeadGroupedAdjacencyList,
kExpectedHeadGroupedIncomingArcList, kExpectedHeadGroupedOutgoingArcList,
kExpectedHeadGroupedDebugString, kExpectedHeadGroupedForwardDebugString,
"");
// Test that the GroupForwardArcsByFunctor method correctly permutes arc
// annotation data.
int arc_annotations[] = {103, 105, 101, 106, 102, 104};
ArrayIndexCycleHandler<int, typename TypeParam::ArcIndex> handler(
arc_annotations);
graph.GroupForwardArcsByFunctor(ArcFunctorByTail<TypeParam>(graph), &handler);
for (int i = 0; i < 6; ++i) {
EXPECT_EQ(101 + i, arc_annotations[i]);
}
const std::string kGraphTailGroupedArcList =
" Arc 0: 0 -> 1\n"
" Arc 1: 0 -> 2\n"
" Arc 2: 1 -> 0\n"
" Arc 3: 1 -> 2\n"
" Arc 4: 2 -> 0\n"
" Arc 5: 2 -> 1\n";
const std::string kExpectedTailGroupedAdjacencyList =
" Node 0:\n"
" Arc -5: 0 -> 2\n"
" Arc -3: 0 -> 1\n"
" Arc 1: 0 -> 2\n"
" Arc 0: 0 -> 1\n"
" Node 1:\n"
" Arc -6: 1 -> 2\n"
" Arc 3: 1 -> 2\n"
" Arc 2: 1 -> 0\n"
" Arc -1: 1 -> 0\n"
" Node 2:\n"
" Arc 5: 2 -> 1\n"
" Arc 4: 2 -> 0\n"
" Arc -4: 2 -> 1\n"
" Arc -2: 2 -> 0\n";
const std::string kExpectedTailGroupedIncomingArcList =
" Node 0:\n"
" Arc 4: 2 -> 0\n"
" Arc 2: 1 -> 0\n"
" Node 1:\n"
" Arc 5: 2 -> 1\n"
" Arc 0: 0 -> 1\n"
" Node 2:\n"
" Arc 3: 1 -> 2\n"
" Arc 1: 0 -> 2\n";
const std::string kExpectedTailGroupedOutgoingArcList =
" Node 0:\n"
" Arc 1: 0 -> 2\n"
" Arc 0: 0 -> 1\n"
" Node 1:\n"
" Arc 3: 1 -> 2\n"
" Arc 2: 1 -> 0\n"
" Node 2:\n"
" Arc 5: 2 -> 1\n"
" Arc 4: 2 -> 0\n";
const std::string kExpectedTailGroupedDebugString =
"Arcs:(node, next arc) :\n"
" -6:(2,3)\n"
" -5:(2,-3)\n"
" -4:(1,-2)\n"
" -3:(1,1)\n"
" -2:(0,NilArc)\n"
" -1:(0,NilArc)\n"
" 0:(1,NilArc)\n"
" 1:(2,0)\n"
" 2:(0,-1)\n"
" 3:(2,2)\n"
" 4:(0,-4)\n"
" 5:(1,4)\n"
"Node:First arc :\n"
" 0:-5\n"
" 1:-6\n"
" 2:5\n";
const std::string kExpectedTailGroupedForwardDebugString =
"Arcs:(node, next arc) :\n"
" 0:(1,NilArc)\n"
" 1:(2,0)\n"
" 2:(0,NilArc)\n"
" 3:(2,2)\n"
" 4:(0,NilArc)\n"
" 5:(1,4)\n"
"Node:First arc :\n"
" 0:1\n"
" 1:3\n"
" 2:5\n";
TestEbertGraph(
graph, kGraphTailGroupedArcList, kExpectedTailGroupedAdjacencyList,
kExpectedTailGroupedIncomingArcList, kExpectedTailGroupedOutgoingArcList,
kExpectedTailGroupedDebugString, kExpectedTailGroupedForwardDebugString,
"");
}
template <typename GraphType>
class DebugStringTestWithGraphBuildManager : public ::testing::Test {};
typedef ::testing::Types<EbertGraph<int16_t, int16_t> >
GraphTypesForDebugStringTestWithGraphBuildManager;
TYPED_TEST_SUITE(DebugStringTestWithGraphBuildManager,
GraphTypesForDebugStringTestWithGraphBuildManager);
TYPED_TEST(DebugStringTestWithGraphBuildManager,
UnsortedArcsWithoutAnnotation) {
AnnotatedGraphBuildManager<TypeParam>* builder =
new AnnotatedGraphBuildManager<TypeParam>(
4, 6, false /* don't sort adjacency lists */);
EXPECT_EQ(0, builder->AddArc(0, 2));
EXPECT_EQ(1, builder->AddArc(2, 0));
EXPECT_EQ(2, builder->AddArc(2, 3));
EXPECT_EQ(3, builder->AddArc(3, 2));
EXPECT_EQ(4, builder->AddArc(0, 3));
EXPECT_EQ(5, builder->AddArc(3, 0));
const TypeParam* graph = builder->Graph(nullptr);
ASSERT_TRUE(graph != nullptr);
const std::string kGraphArcList =
" Arc 0: 0 -> 2\n"
" Arc 1: 2 -> 0\n"
" Arc 2: 2 -> 3\n"
" Arc 3: 3 -> 2\n"
" Arc 4: 0 -> 3\n"
" Arc 5: 3 -> 0\n";
const std::string kExpectedAdjacencyList =
" Node 0:\n"
" Arc -6: 0 -> 3\n"
" Arc 4: 0 -> 3\n"
" Arc -2: 0 -> 2\n"
" Arc 0: 0 -> 2\n"
" Node 1:\n"
" Node 2:\n"
" Arc -4: 2 -> 3\n"
" Arc 2: 2 -> 3\n"
" Arc 1: 2 -> 0\n"
" Arc -1: 2 -> 0\n"
" Node 3:\n"
" Arc 5: 3 -> 0\n"
" Arc -5: 3 -> 0\n"
" Arc 3: 3 -> 2\n"
" Arc -3: 3 -> 2\n";
const std::string kExpectedIncomingArcList =
" Node 0:\n"
" Arc 5: 3 -> 0\n"
" Arc 1: 2 -> 0\n"
" Node 1:\n"
" Node 2:\n"
" Arc 3: 3 -> 2\n"
" Arc 0: 0 -> 2\n"
" Node 3:\n"
" Arc 4: 0 -> 3\n"
" Arc 2: 2 -> 3\n";
const std::string kExpectedOutgoingArcList =
" Node 0:\n"
" Arc 4: 0 -> 3\n"
" Arc 0: 0 -> 2\n"
" Node 1:\n"
" Node 2:\n"
" Arc 2: 2 -> 3\n"
" Arc 1: 2 -> 0\n"
" Node 3:\n"
" Arc 5: 3 -> 0\n"
" Arc 3: 3 -> 2\n";
const std::string kExpectedDebugString =
"Arcs:(node, next arc) :\n"
" -6:(3,4)\n"
" -5:(0,3)\n"
" -4:(3,2)\n"
" -3:(2,NilArc)\n"
" -2:(2,0)\n"
" -1:(0,NilArc)\n"
" 0:(2,NilArc)\n"
" 1:(0,-1)\n"
" 2:(3,1)\n"
" 3:(2,-3)\n"
" 4:(3,-2)\n"
" 5:(0,-5)\n"
"Node:First arc :\n"
" 0:-6\n"
" 1:NilArc\n"
" 2:-4\n"
" 3:5\n";
const std::string kExpectedForwardDebugString =
"Arcs:(node, next arc) :\n"
" 0:(2,NilArc)\n"
" 1:(0,NilArc)\n"
" 2:(3,1)\n"
" 3:(2,NilArc)\n"
" 4:(3,0)\n"
" 5:(0,3)\n"
"Node:First arc :\n"
" 0:4\n"
" 1:NilArc\n"
" 2:2\n"
" 3:5\n";
const std::string kExpectedForwardStaticDebugString =
"Arcs:(node) :\n"
" 0:(2)\n"
" 1:(3)\n"
" 2:(0)\n"
" 3:(3)\n"
" 4:(2)\n"
" 5:(0)\n"
"Node:First arc :\n"
" 0:0\n"
" 1:2\n"
" 2:2\n"
" 3:4\n"
" 4:6\n";
TestEbertGraph(*graph, kGraphArcList, kExpectedAdjacencyList,
kExpectedIncomingArcList, kExpectedOutgoingArcList,
kExpectedDebugString, kExpectedForwardDebugString,
kExpectedForwardStaticDebugString);
delete graph;
}
TYPED_TEST(DebugStringTestWithGraphBuildManager, SortedArcsWithAnnotation) {
AnnotatedGraphBuildManager<TypeParam>* builder =
new AnnotatedGraphBuildManager<TypeParam>(
4, 6, true /* sort adjacency lists */);
EXPECT_EQ(0, builder->AddArc(0, 2));
EXPECT_EQ(1, builder->AddArc(2, 0));
EXPECT_EQ(2, builder->AddArc(2, 3));
EXPECT_EQ(3, builder->AddArc(3, 2));
EXPECT_EQ(4, builder->AddArc(0, 3));
EXPECT_EQ(5, builder->AddArc(3, 0));
// Test that the graph building and arc sorting operations correctly
// permute arc annotation data.
int arc_annotations[] = {101, 103, 104, 106, 102, 105};
ArrayIndexCycleHandler<int, typename TypeParam::ArcIndex> handler(
arc_annotations);
const TypeParam* graph = builder->Graph(&handler);
ASSERT_TRUE(graph != nullptr);
for (int i = 0; i < 6; ++i) {
EXPECT_EQ(101 + i, arc_annotations[i]);
}
const std::string kGraphArcList =
" Arc 0: 0 -> 2\n"
" Arc 1: 0 -> 3\n"
" Arc 2: 2 -> 0\n"
" Arc 3: 2 -> 3\n"
" Arc 4: 3 -> 0\n"
" Arc 5: 3 -> 2\n";
const std::string kExpectedAdjacencyList =
" Node 0:\n"
" Arc -5: 0 -> 3\n"
" Arc -3: 0 -> 2\n"
" Arc 1: 0 -> 3\n"
" Arc 0: 0 -> 2\n"
" Node 1:\n"
" Node 2:\n"
" Arc -6: 2 -> 3\n"
" Arc 3: 2 -> 3\n"
" Arc 2: 2 -> 0\n"
" Arc -1: 2 -> 0\n"
" Node 3:\n"
" Arc 5: 3 -> 2\n"
" Arc 4: 3 -> 0\n"
" Arc -4: 3 -> 2\n"
" Arc -2: 3 -> 0\n";
const std::string kExpectedIncomingArcList =
" Node 0:\n"
" Arc 4: 3 -> 0\n"
" Arc 2: 2 -> 0\n"
" Node 1:\n"
" Node 2:\n"
" Arc 5: 3 -> 2\n"
" Arc 0: 0 -> 2\n"
" Node 3:\n"
" Arc 3: 2 -> 3\n"
" Arc 1: 0 -> 3\n";
const std::string kExpectedOutgoingArcList =
" Node 0:\n"
" Arc 1: 0 -> 3\n"
" Arc 0: 0 -> 2\n"
" Node 1:\n"
" Node 2:\n"
" Arc 3: 2 -> 3\n"
" Arc 2: 2 -> 0\n"
" Node 3:\n"
" Arc 5: 3 -> 2\n"
" Arc 4: 3 -> 0\n";
const std::string kExpectedDebugString =
"Arcs:(node, next arc) :\n"
" -6:(3,3)\n"
" -5:(3,-3)\n"
" -4:(2,-2)\n"
" -3:(2,1)\n"
" -2:(0,NilArc)\n"
" -1:(0,NilArc)\n"
" 0:(2,NilArc)\n"
" 1:(3,0)\n"
" 2:(0,-1)\n"
" 3:(3,2)\n"
" 4:(0,-4)\n"
" 5:(2,4)\n"
"Node:First arc :\n"
" 0:-5\n"
" 1:NilArc\n"
" 2:-6\n"
" 3:5\n";
const std::string kExpectedForwardDebugString =
"Arcs:(node, next arc) :\n"
" 0:(2,NilArc)\n"
" 1:(3,0)\n"
" 2:(0,NilArc)\n"
" 3:(3,2)\n"
" 4:(0,NilArc)\n"
" 5:(2,4)\n"
"Node:First arc :\n"
" 0:1\n"
" 1:NilArc\n"
" 2:3\n"
" 3:5\n";
const std::string kExpectedForwardStaticDebugString =
"Arcs:(node) :\n"
" 0:(2)\n"
" 1:(3)\n"
" 2:(0)\n"
" 3:(3)\n"
" 4:(0)\n"
" 5:(2)\n"
"Node:First arc :\n"
" 0:0\n"
" 1:2\n"
" 2:2\n"
" 3:4\n"
" 4:6\n";
TestEbertGraph(*graph, kGraphArcList, kExpectedAdjacencyList,
kExpectedIncomingArcList, kExpectedOutgoingArcList,
kExpectedDebugString, kExpectedForwardDebugString,
kExpectedForwardStaticDebugString);
delete graph;
}
TYPED_TEST(DebugStringTestWithGraphBuildManager, SortedArcsWithoutAnnotation) {
AnnotatedGraphBuildManager<TypeParam>* builder =
new AnnotatedGraphBuildManager<TypeParam>(
4, 6, true /* sort adjacency lists */);
EXPECT_EQ(0, builder->AddArc(0, 2));
EXPECT_EQ(1, builder->AddArc(2, 0));
EXPECT_EQ(2, builder->AddArc(2, 3));
EXPECT_EQ(3, builder->AddArc(3, 2));
EXPECT_EQ(4, builder->AddArc(0, 3));
EXPECT_EQ(5, builder->AddArc(3, 0));
const TypeParam* graph = builder->Graph(nullptr);
const std::string kGraphArcList =
" Arc 0: 0 -> 2\n"
" Arc 1: 0 -> 3\n"
" Arc 2: 2 -> 0\n"
" Arc 3: 2 -> 3\n"
" Arc 4: 3 -> 0\n"
" Arc 5: 3 -> 2\n";
const std::string kExpectedAdjacencyList =
" Node 0:\n"
" Arc -5: 0 -> 3\n"
" Arc -3: 0 -> 2\n"
" Arc 1: 0 -> 3\n"
" Arc 0: 0 -> 2\n"
" Node 1:\n"
" Node 2:\n"
" Arc -6: 2 -> 3\n"
" Arc 3: 2 -> 3\n"
" Arc 2: 2 -> 0\n"
" Arc -1: 2 -> 0\n"
" Node 3:\n"
" Arc 5: 3 -> 2\n"
" Arc 4: 3 -> 0\n"
" Arc -4: 3 -> 2\n"
" Arc -2: 3 -> 0\n";
const std::string kExpectedIncomingArcList =
" Node 0:\n"
" Arc 4: 3 -> 0\n"
" Arc 2: 2 -> 0\n"
" Node 1:\n"
" Node 2:\n"
" Arc 5: 3 -> 2\n"
" Arc 0: 0 -> 2\n"
" Node 3:\n"
" Arc 3: 2 -> 3\n"
" Arc 1: 0 -> 3\n";
const std::string kExpectedOutgoingArcList =
" Node 0:\n"
" Arc 1: 0 -> 3\n"
" Arc 0: 0 -> 2\n"
" Node 1:\n"
" Node 2:\n"
" Arc 3: 2 -> 3\n"
" Arc 2: 2 -> 0\n"
" Node 3:\n"
" Arc 5: 3 -> 2\n"
" Arc 4: 3 -> 0\n";
const std::string kExpectedDebugString =
"Arcs:(node, next arc) :\n"
" -6:(3,3)\n"
" -5:(3,-3)\n"
" -4:(2,-2)\n"
" -3:(2,1)\n"
" -2:(0,NilArc)\n"
" -1:(0,NilArc)\n"
" 0:(2,NilArc)\n"
" 1:(3,0)\n"
" 2:(0,-1)\n"
" 3:(3,2)\n"
" 4:(0,-4)\n"
" 5:(2,4)\n"
"Node:First arc :\n"
" 0:-5\n"
" 1:NilArc\n"
" 2:-6\n"
" 3:5\n";
const std::string kExpectedForwardDebugString =
"Arcs:(node, next arc) :\n"
" 0:(2,NilArc)\n"
" 1:(3,0)\n"
" 2:(0,NilArc)\n"
" 3:(3,2)\n"
" 4:(0,NilArc)\n"
" 5:(2,4)\n"
"Node:First arc :\n"
" 0:1\n"
" 1:NilArc\n"
" 2:3\n"
" 3:5\n";
const std::string kExpectedForwardStaticDebugString =
"Arcs:(node) :\n"
" 0:(2)\n"
" 1:(3)\n"
" 2:(0)\n"
" 3:(3)\n"
" 4:(0)\n"
" 5:(2)\n"
"Node:First arc :\n"
" 0:0\n"
" 1:2\n"
" 2:2\n"
" 3:4\n"
" 4:6\n";
TestEbertGraph(*graph, kGraphArcList, kExpectedAdjacencyList,
kExpectedIncomingArcList, kExpectedOutgoingArcList,
kExpectedDebugString, kExpectedForwardDebugString,
kExpectedForwardStaticDebugString);
delete graph;
}
// An empty fixture template to collect the types of tiny graphs for which we
// want to do very basic tests.
template <typename GraphType>
class TinyEbertGraphTest : public ::testing::Test {};
typedef ::testing::Types<EbertGraph<int8_t, int8_t> >
TinyEbertGraphTypesForTesting;
TYPED_TEST_SUITE(TinyEbertGraphTest, TinyEbertGraphTypesForTesting);
TYPED_TEST(TinyEbertGraphTest, CheckDeathOnBadBounds) {
typedef TypeParam SmallStarGraph;
int num_nodes = SmallStarGraph::kMaxNumNodes;
int num_arcs = SmallStarGraph::kMaxNumArcs;
SmallStarGraph(num_nodes, num_arcs); // Construct an unused graph. All fine.
}
// An empty fixture to collect the types of small graphs for which we want to do
// some fairly trivial tests.
template <typename GraphType>
class SmallEbertGraphTest : public ::testing::Test {};
typedef ::testing::Types<EbertGraph<int8_t, int8_t>,
EbertGraph<int16_t, int16_t> >
SmallEbertGraphTypesForTesting;
TYPED_TEST_SUITE(SmallEbertGraphTest, SmallEbertGraphTypesForTesting);
TYPED_TEST(SmallEbertGraphTest, EmptyGraph) {
TypeParam graph(3, 6);
const std::string kGraphArcList = "";
const std::string kExpectedAdjacencyList = "";
const std::string kExpectedIncomingArcList = "";
const std::string kExpectedOutgoingArcList = "";
const std::string kExpectedDebugString =
"Arcs:(node, next arc) :\n"
"Node:First arc :\n";
TestEbertGraph(graph, kGraphArcList, kExpectedAdjacencyList,
kExpectedIncomingArcList, kExpectedOutgoingArcList,
kExpectedDebugString, kExpectedDebugString,
kExpectedDebugString);
}
TEST(EbertGraphTest, CheckBounds) {
typedef EbertGraph<int16_t, int16_t> SmallStarGraph;
SmallStarGraph g(SmallStarGraph::kMaxNumNodes, SmallStarGraph::kMaxNumArcs);
EXPECT_TRUE(g.CheckArcBounds(SmallStarGraph::kNilArc));
EXPECT_FALSE(g.CheckArcValidity(SmallStarGraph::kNilArc));
EXPECT_FALSE(g.CheckArcValidity(SmallStarGraph::kMaxNumArcs));
EXPECT_TRUE(g.CheckArcValidity(g.SmallStarGraph::kMaxNumArcs - 1));
EXPECT_TRUE(g.CheckArcValidity(g.Opposite(SmallStarGraph::kMaxNumArcs - 1)));
}
template <typename GraphType, bool sort_arcs>
static void BM_RandomArcs(benchmark::State& state) {
const int kRandomSeed = 0;
const int kNodes = 10 * 1000 * 1000;
const int kArcs = 5 * kNodes;
for (auto _ : state) {
AnnotatedGraphBuildManager<GraphType>* builder =
new AnnotatedGraphBuildManager<GraphType>(kNodes, kArcs, sort_arcs);
std::mt19937 randomizer(kRandomSeed);
for (int i = 0; i < kArcs; ++i) {
builder->AddArc(absl::Uniform(randomizer, 0, kNodes),
absl::Uniform(randomizer, 0, kNodes));
}
(void)builder->Graph(nullptr);
}
// An item is an arc here.
state.SetItemsProcessed(static_cast<int64_t>(state.max_iterations) * kArcs);
}
BENCHMARK_TEMPLATE2(BM_RandomArcs, StarGraph, false);
BENCHMARK_TEMPLATE2(BM_RandomArcs, StarGraph, true);
template <typename GraphType, bool sort_arcs>
static void BM_RandomAnnotatedArcs(benchmark::State& state) {
const int kRandomSeed = 0;
const int kNodes = 10 * 1000 * 1000;
const int kArcs = 5 * kNodes;
int* annotation = new int[kArcs];
for (auto _ : state) {
AnnotatedGraphBuildManager<GraphType>* builder =
new AnnotatedGraphBuildManager<GraphType>(kNodes, kArcs, sort_arcs);
std::mt19937 randomizer(kRandomSeed);
for (int i = 0; i < kArcs; ++i) {
ArcIndex arc = builder->AddArc(absl::Uniform(randomizer, 0, kNodes),
absl::Uniform(randomizer, 0, kNodes));
annotation[arc] = absl::Uniform(randomizer, 0, kNodes);
}
ArrayIndexCycleHandler<int, ArcIndex> cycle_handler(annotation);
(void)builder->Graph(&cycle_handler);
}
delete[] annotation;
// An item is an arc here.
state.SetItemsProcessed(static_cast<int64_t>(state.max_iterations) * kArcs);
}
BENCHMARK_TEMPLATE2(BM_RandomAnnotatedArcs, StarGraph, false);
BENCHMARK_TEMPLATE2(BM_RandomAnnotatedArcs, StarGraph, true);
template <typename GraphType>
static void BM_AddRandomArcsAndDoNotRetrieveGraph(benchmark::State& state) {
const int kRandomSeed = 0;
const int kNodes = 10 * 1000 * 1000;
const int kArcs = 5 * kNodes;
for (auto _ : state) {
AnnotatedGraphBuildManager<GraphType>* builder =
new AnnotatedGraphBuildManager<GraphType>(kNodes, kArcs, false);
std::mt19937 randomizer(kRandomSeed);
for (int i = 0; i < kArcs; ++i) {
builder->AddArc(absl::Uniform(randomizer, 0, kNodes),
absl::Uniform(randomizer, 0, kNodes));
}
delete builder;
}
// An item is an arc here.
state.SetItemsProcessed(static_cast<int64_t>(state.max_iterations) * kArcs);
}
BENCHMARK_TEMPLATE(BM_AddRandomArcsAndDoNotRetrieveGraph, StarGraph);
} // namespace operations_research