// 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/graph.h" #include #include #include #include #include #include #include #include #include #include #include "absl/log/check.h" #include "absl/random/random.h" #include "absl/strings/str_cat.h" #include "absl/types/span.h" #include "benchmark/benchmark.h" #include "gtest/gtest.h" #include "ortools/base/gmock.h" #include "ortools/base/strong_int.h" namespace util { using testing::Pair; using testing::UnorderedElementsAre; DEFINE_STRONG_INT_TYPE(StrongNodeId, int32_t); DEFINE_STRONG_INT_TYPE(StrongArcId, int32_t); // Iterators. #if __cplusplus >= 202002L static_assert(std::forward_iterator::OutgoingArcIterator>); static_assert(std::forward_iterator::OutgoingHeadIterator>); static_assert( std::forward_iterator::OutgoingArcIterator>); static_assert( std::forward_iterator::IncomingArcIterator>); static_assert(std::input_iterator< ReverseArcListGraph<>::OutgoingOrOppositeIncomingArcIterator>); static_assert( std::forward_iterator::OutgoingHeadIterator>); #endif // __cplusplus >= 202002L // GraphTraits. static_assert( std::is_same_v>::NodeIndex, int32_t>); static_assert( std::is_same_v< typename GraphTraits>::NodeIndex, int16_t>); static_assert(std::is_same_v< typename GraphTraits>::NodeIndex, uint32_t>); static_assert( std::is_same_v< typename GraphTraits>::NodeIndex, StrongNodeId>); static_assert( std::is_same_v< typename GraphTraits>>::NodeIndex, int>); // Check that the OutgoingArcs() returns exactly the same arcs as the verifier. // This also test Head(), Tail(), and OutDegree(). template void CheckOutgoingArcIterator( const GraphType& graph, absl::Span> verifier) { using NodeIndex = typename GraphType::NodeIndex; using ArcIndex = typename GraphType::ArcIndex; std::vector node_seen(verifier.size(), 0); for (int i = 0; i < verifier.size(); ++i) { for (int j = 0; j < verifier[i].size(); ++j) { // We have to use int because there can be multiple arcs. node_seen[static_cast(verifier[i][j])]++; } int outgoing_arc_number = 0; for (const ArcIndex arc : graph.OutgoingArcs(NodeIndex(i))) { const int head = static_cast(graph.Head(arc)); const int tail = static_cast(graph.Tail(arc)); EXPECT_GE(head, 0); EXPECT_LT(head, verifier.size()); EXPECT_GT(node_seen[head], 0); node_seen[head]--; EXPECT_EQ(i, tail); EXPECT_EQ(arc, *(graph.OutgoingArcsStartingFrom(NodeIndex(i), arc).begin())); ++outgoing_arc_number; } // If this is true, then node_seen must have been cleaned. EXPECT_EQ(verifier[i].size(), outgoing_arc_number); EXPECT_EQ(ArcIndex(verifier[i].size()), graph.OutDegree(NodeIndex(i))); } } // Check that the operator[] returns exactly the same nodes as the verifier. template void CheckOutgoingHeadIterator( const GraphType& graph, absl::Span> verifier) { using NodeIndex = typename GraphType::NodeIndex; using ArcIndex = typename GraphType::ArcIndex; std::vector node_seen(verifier.size(), 0); for (int i = 0; i < verifier.size(); ++i) { for (int j = 0; j < verifier[i].size(); ++j) { // We have to use int because there can be multiple arcs. node_seen[static_cast(verifier[i][j])]++; } int outgoing_head_number = 0; for (const NodeIndex node : graph[NodeIndex(i)]) { const int node_id = static_cast(node); EXPECT_GE(node_id, 0); EXPECT_LT(node_id, verifier.size()); EXPECT_GT(node_seen[node_id], 0); node_seen[node_id]--; ++outgoing_head_number; } // If this is true, then node_seen must have been cleaned. EXPECT_EQ(verifier[i].size(), outgoing_head_number); EXPECT_EQ(ArcIndex(verifier[i].size()), graph.OutDegree(NodeIndex(i))); } } // Check that the heads of OutgoingArcs() + the tails of IncomingArcs() are the // same as the heads of OutgoingOrOppositeIncomingArcs(). Also perform // various checks on the arcs. template void CheckReverseArcIterator(const GraphType& graph) { using NodeIndex = typename GraphType::NodeIndex; using ArcIndex = typename GraphType::ArcIndex; ArcIndex total_arc_number(0); std::vector node_seen(static_cast(graph.num_nodes()), 0); for (const NodeIndex node : graph.AllNodes()) { ArcIndex num_incident_arcs(0); for (const ArcIndex arc : graph.OutgoingOrOppositeIncomingArcs(node)) { EXPECT_EQ(node, graph.Tail(arc)); EXPECT_EQ(arc, *(graph.OutgoingOrOppositeIncomingArcsStartingFrom(node, arc) .begin())); node_seen[static_cast(graph.Head(arc))]++; ++num_incident_arcs; } total_arc_number += num_incident_arcs; ArcIndex num_outgoing_arcs(0); for (const ArcIndex arc : graph.OutgoingArcs(node)) { EXPECT_GE(arc, ArcIndex(0)); EXPECT_EQ(node, graph.Tail(arc)); EXPECT_EQ(arc, *(graph.OutgoingArcsStartingFrom(node, arc).begin())); const size_t head = static_cast(graph.Head(arc)); EXPECT_GE(node_seen[head], 0); node_seen[head]--; ++num_outgoing_arcs; } EXPECT_EQ(num_outgoing_arcs, graph.OutDegree(node)); ArcIndex num_incoming_arcs(0); for (const ArcIndex arc : graph.IncomingArcs(node)) { EXPECT_GE(arc, ArcIndex(0)); EXPECT_EQ(node, graph.Head(arc)); EXPECT_EQ(arc, *(graph.IncomingArcsStartingFrom(node, arc).begin())); const size_t tail = static_cast(graph.Tail(arc)); node_seen[tail]--; EXPECT_GE(node_seen[tail], 0); ++num_incoming_arcs; } EXPECT_EQ(num_incoming_arcs, graph.InDegree(node)); // If this is true, then node_seen must have been cleaned. EXPECT_EQ(num_incident_arcs, num_outgoing_arcs + num_incoming_arcs); } EXPECT_EQ(2 * graph.num_arcs(), total_arc_number); } // Check that the arcs returned by OppositeIncomingArcs() are exactly the // reverse of the arcs returned by IncomingArcs(). template void CheckOppositeIncomingArcs(const GraphType& graph) { using NodeIndex = typename GraphType::NodeIndex; using ArcIndex = typename GraphType::ArcIndex; std::vector arcs; std::vector opposite_arcs; for (const NodeIndex node : graph.AllNodes()) { arcs.clear(); opposite_arcs.clear(); for (const ArcIndex arc : graph.IncomingArcs(node)) { arcs.push_back(arc); } for (const ArcIndex arc : graph.OppositeIncomingArcs(node)) { opposite_arcs.push_back(arc); } ASSERT_EQ(arcs.size(), opposite_arcs.size()); for (int a = 0; a < arcs.size(); ++a) { ASSERT_EQ(opposite_arcs[a], graph.OppositeArc(arcs[a])); } } } template void CheckReverseArc(const GraphType& graph) {} template void CheckReverseArc( const ReverseArcListGraph& graph) { CheckReverseArcIterator(graph); CheckOppositeIncomingArcs(graph); } template void CheckReverseArc( const ReverseArcStaticGraph& graph) { CheckReverseArcIterator(graph); CheckOppositeIncomingArcs(graph); } // Check that arc annotation can be permuted properly. This is achieved // by "annotating" the original arc index with the head and tail information // and checking that after permutation the annotation of a given arc index // matches is actual head and tail in the graph. template void CheckArcIndexPermutation( const GraphType& graph, const std::vector& permutation, const std::vector& heads, const std::vector& tails) { using NodeIndex = typename GraphType::NodeIndex; using ArcIndex = typename GraphType::ArcIndex; std::vector annotation_h(heads); std::vector annotation_t(tails); Permute(permutation, &annotation_h); Permute(permutation, &annotation_t); for (ArcIndex arc : graph.AllForwardArcs()) { CHECK_EQ(annotation_h[static_cast(arc)], graph.Head(arc)); CHECK_EQ(annotation_t[static_cast(arc)], graph.Tail(arc)); } } template void ConstructAndCheckGraph( const typename GraphType::NodeIndex num_nodes, const typename GraphType::ArcIndex num_arcs, const std::vector& heads, const std::vector& tails, bool reserve, bool test_permutation) { using NodeIndex = typename GraphType::NodeIndex; using ArcIndex = typename GraphType::ArcIndex; std::unique_ptr graph; if (reserve) { graph.reset(new GraphType(num_nodes, num_arcs)); } else { graph.reset(new GraphType()); } std::vector> verifier(static_cast(num_nodes)); for (ArcIndex i(0); i < num_arcs; ++i) { NodeIndex head = heads[static_cast(i)]; NodeIndex tail = tails[static_cast(i)]; EXPECT_EQ(i, graph->AddArc(tail, head)); verifier[static_cast(tail)].push_back(head); } std::vector permutation; if (test_permutation) { graph->Build(&permutation); } else { graph->Build(); } EXPECT_EQ(num_nodes, graph->num_nodes()); EXPECT_EQ(num_nodes, graph->size()); EXPECT_EQ(num_arcs, graph->num_arcs()); CheckOutgoingArcIterator(*graph, verifier); CheckOutgoingHeadIterator(*graph, verifier); if (test_permutation) { CheckArcIndexPermutation(*graph, permutation, heads, tails); } CheckReverseArc(*graph); } // Return the size of the memory block allocated by malloc when asking for x // bytes. inline int UpperBoundOfMallocBlockSizeOf(int x) { // Note(user): as of 2012-09, the rule seems to be: round x up to the // next multiple of 16. // WARNING: This may change, and may already be wrong for small values. return 16 * ((x + 15) / 16); } TEST(SVectorTest, DynamicGrowth) { internal::SVector v; EXPECT_EQ(0, v.size()); EXPECT_EQ(0, v.capacity()); for (int i = 0; i < 100; i++) { v.grow(-i, i); } EXPECT_EQ(100, v.size()); EXPECT_GE(v.capacity(), 100); EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(100)); for (int i = 0; i < 100; i++) { EXPECT_EQ(-i, v[~i]); EXPECT_EQ(i, v[i]); } } TEST(SVectorTest, Reserve) { internal::SVector v; v.reserve(100); EXPECT_EQ(0, v.size()); EXPECT_GE(v.capacity(), 100); EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(100)); for (int i = 0; i < 100; i++) { v.grow(-i, i); } EXPECT_EQ(100, v.size()); EXPECT_GE(v.capacity(), 100); EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(100)); for (int i = 0; i < 10; i++) { EXPECT_EQ(-i, v[~i]); EXPECT_EQ(i, v[i]); } } TEST(SVectorTest, Resize) { internal::SVector v; v.resize(100); EXPECT_EQ(100, v.size()); EXPECT_GE(v.capacity(), 100); EXPECT_LE(v.capacity(), UpperBoundOfMallocBlockSizeOf(100)); for (int i = 0; i < 100; i++) { EXPECT_EQ(0, v[-i - 1]); EXPECT_EQ(0, v[i]); } } TEST(SVectorTest, ResizeToZero) { internal::SVector s; s.resize(1); s.resize(0); EXPECT_EQ(0, s.size()); } TEST(SVectorTest, Swap) { internal::SVector s; internal::SVector t; s.resize(1); s[0] = 's'; s[-1] = 's'; t.resize(2); for (int i = -2; i <= 1; ++i) { t[i] = 't'; } s.swap(t); EXPECT_EQ(1, t.size()); EXPECT_EQ('s', t[-1]); EXPECT_EQ('s', t[0]); EXPECT_EQ(2, s.size()); EXPECT_EQ('t', s[-2]); EXPECT_EQ('t', s[-1]); EXPECT_EQ('t', s[0]); EXPECT_EQ('t', s[1]); } TEST(SVectorTest, SwapAndDestroy) { internal::SVector s; { internal::SVector t; t.resize(2); t[-2] = 42; t.swap(s); } EXPECT_EQ(2, s.size()); EXPECT_EQ(42, s[-2]); EXPECT_EQ(0, s[1]); } // Use a more complex type to better check the invocations of // constructors/destructors. TEST(SVectorStringTest, DynamicSize) { internal::SVector s; s.resize(10); for (int i = 0; i < 10; ++i) { s[i] = "Right"; s[~i] = "Left"; } ASSERT_LT(s.capacity(), 50); for (int i = 0; i < 50; ++i) s.grow("NewLeft", "NewRight"); s.resize(10); for (int i = 0; i < 50; ++i) s.grow("NewNewLeft", "NewNewRight"); for (int i = 0; i < 10; ++i) { EXPECT_EQ("Left", s[-i - 1]); EXPECT_EQ("Right", s[i]); } for (int i = 10; i < 10 + 50; ++i) { EXPECT_EQ("NewNewLeft", s[-i - 1]); EXPECT_EQ("NewNewRight", s[i]); } } // An object that supports moves but not copies. It also has non-trivial // default constructor, and a non-trivial destructor, and makes various internal // consistency checks that help flush out bugs (double destruction, failure to // destruct, etc.). class MoveOnlyObject { public: MoveOnlyObject() : id_(std::make_unique(sequence_++)) { ++object_count_; Validate(); } ~MoveOnlyObject() { Validate(); --object_count_; CHECK_GE(object_count_, 0); } MoveOnlyObject(const MoveOnlyObject&) = delete; MoveOnlyObject(MoveOnlyObject&& other) : id_(std::move(other.id_)) { ++object_count_; other.id_ = std::make_unique(sequence_++); Validate(); other.Validate(); } MoveOnlyObject& operator=(const MoveOnlyObject&) = delete; MoveOnlyObject& operator=(MoveOnlyObject&& other) { using std::swap; swap(id_, other.id_); Validate(); other.Validate(); return *this; } static int GetObjectCount() { return object_count_; } private: void Validate() { // Every MoveOnlyObject, even after it has been moved from, has a valid // non-null id_. EXPECT_TRUE(id_ != nullptr); if (id_ != nullptr) { EXPECT_GT(*id_, 0); EXPECT_LT(*id_, sequence_); } } static int sequence_; static int object_count_; std::unique_ptr id_; }; int MoveOnlyObject::sequence_ = 1; int MoveOnlyObject::object_count_ = 0; TEST(SVectorTest, MoveWithMoveOnlyObject) { EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); internal::SVector a; a.resize(10); EXPECT_EQ(10, a.size()); EXPECT_EQ(20, MoveOnlyObject::GetObjectCount()); internal::SVector b = std::move(a); EXPECT_EQ(10, b.size()); EXPECT_EQ(20, MoveOnlyObject::GetObjectCount()); // Suppress the bugprone-use-after-move clang-tidy warning on `a` EXPECT_EQ(0, a.size()); // NOLINT } TEST(SVectorTest, ShrinkWithMoveOnlyObject) { EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); { internal::SVector a; a.resize(10); EXPECT_EQ(20, MoveOnlyObject::GetObjectCount()); a.resize(5); EXPECT_EQ(10, MoveOnlyObject::GetObjectCount()); } EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); } TEST(SVectorTest, GrowMoveOnlyObject) { EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); { internal::SVector a; a.resize(10); EXPECT_EQ(a.size() * 2, MoveOnlyObject::GetObjectCount()); // Grow to the point where the vector reallocates. MoveOnlyObject* const original_data = a.data(); while (original_data == a.data()) { a.resize(a.size() + 1); EXPECT_EQ(a.size() * 2, MoveOnlyObject::GetObjectCount()); } } EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); } TEST(SVectorTest, ReserveMoveOnlyObject) { EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); { internal::SVector a; a.resize(10); EXPECT_EQ(a.size() * 2, MoveOnlyObject::GetObjectCount()); // Reserve to the point where the vector reallocates. MoveOnlyObject* const original_data = a.data(); while (original_data == a.data()) { a.reserve(a.size() * 2); EXPECT_EQ(a.size() * 2, MoveOnlyObject::GetObjectCount()); } } EXPECT_EQ(0, MoveOnlyObject::GetObjectCount()); } struct TrackedObject { static int num_constructions; static int num_destructions; static int num_moves; static int num_copies; static void ResetCounters() { num_constructions = 0; num_destructions = 0; num_moves = 0; num_copies = 0; } static std::string Counters() { return absl::StrCat("constructions: ", num_constructions, ", destructions: ", num_destructions, ", moves: ", num_moves, ", copies: ", num_copies); } TrackedObject() { ++num_constructions; } ~TrackedObject() { ++num_destructions; } TrackedObject(TrackedObject&&) { ++num_moves; } TrackedObject(const TrackedObject&) { ++num_copies; } TrackedObject& operator=(const TrackedObject&) { ++num_copies; return *this; } TrackedObject& operator=(TrackedObject&&) { ++num_moves; return *this; } }; int TrackedObject::num_constructions = 0; int TrackedObject::num_destructions = 0; int TrackedObject::num_moves = 0; int TrackedObject::num_copies = 0; TEST(SVectorTest, CopyConstructor) { TrackedObject::ResetCounters(); ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); auto v = std::make_unique>(); ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); v->resize(5); ASSERT_EQ(TrackedObject::Counters(), "constructions: 10, destructions: 0, moves: 0, copies: 0"); auto v_copy(*v); ASSERT_EQ(TrackedObject::Counters(), "constructions: 10, destructions: 0, moves: 0, copies: 10"); v.reset(nullptr); ASSERT_EQ(TrackedObject::Counters(), "constructions: 10, destructions: 10, moves: 0, copies: 10"); ASSERT_EQ(v_copy.size(), 5); } TEST(SVectorTest, AssignmentOperator) { TrackedObject::ResetCounters(); ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); auto v = std::make_unique>(); ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); v->resize(5); ASSERT_EQ(TrackedObject::Counters(), "constructions: 10, destructions: 0, moves: 0, copies: 0"); internal::SVector other; ASSERT_EQ(TrackedObject::Counters(), "constructions: 10, destructions: 0, moves: 0, copies: 0"); other = *v; ASSERT_EQ(TrackedObject::Counters(), "constructions: 10, destructions: 0, moves: 0, copies: 10"); v.reset(nullptr); ASSERT_EQ(TrackedObject::Counters(), "constructions: 10, destructions: 10, moves: 0, copies: 10"); ASSERT_EQ(other.size(), 5); } TEST(SVectorTest, CopyConstructorIntegralType) { auto v = internal::SVector(); v.resize(3); v[-3] = 1; v[-2] = 2; v[-1] = 3; v[0] = 1; v[1] = 2; v[2] = 3; auto other = internal::SVector(v); ASSERT_EQ(v.size(), other.size()); for (int i = -v.size(); i < v.size(); i++) { ASSERT_EQ(v[i], other[i]); } } TEST(SVectorTest, AssignmentOperatorIntegralType) { internal::SVector other; auto v = internal::SVector(); v.resize(3); v[-3] = 1; v[-2] = 2; v[-1] = 3; v[0] = 1; v[1] = 2; v[2] = 3; other = v; ASSERT_EQ(v.size(), other.size()); for (int i = -v.size(); i < v.size(); i++) { ASSERT_EQ(v[i], other[i]); } } TEST(SVectorTest, MoveConstructor) { TrackedObject::ResetCounters(); ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); internal::SVector a; ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); a.resize(5); ASSERT_EQ(TrackedObject::Counters(), "constructions: 10, destructions: 0, moves: 0, copies: 0"); internal::SVector b = std::move(a); // We don't expect any moves of the individual elements, because the // containers just swap their memory buffers. ASSERT_EQ(TrackedObject::Counters(), "constructions: 10, destructions: 0, moves: 0, copies: 0"); ASSERT_EQ(b.size(), 5); } TEST(SVectorTest, MoveAssignmentOperator) { TrackedObject::ResetCounters(); ASSERT_EQ(TrackedObject::Counters(), "constructions: 0, destructions: 0, moves: 0, copies: 0"); internal::SVector a; a.resize(3); ASSERT_EQ(TrackedObject::Counters(), "constructions: 6, destructions: 0, moves: 0, copies: 0"); { internal::SVector b; b.resize(5); ASSERT_EQ(TrackedObject::Counters(), "constructions: 16, destructions: 0, moves: 0, copies: 0"); a = std::move(b); // Ditto: the containers swap themselves. But we do trigger the destruction // of the underlying elements of the destination vector immediately. ASSERT_EQ(TrackedObject::Counters(), "constructions: 16, destructions: 6, moves: 0, copies: 0"); } ASSERT_EQ(a.size(), 5); } template class GenericGraphInterfaceTest : public ::testing::Test {}; typedef ::testing::Types< ListGraph, ListGraph, ListGraph, ListGraph, ListGraph, ReverseArcListGraph, ReverseArcListGraph, ReverseArcListGraph, ReverseArcListGraph, ReverseArcListGraph, ReverseArcStaticGraph, ReverseArcStaticGraph, ReverseArcStaticGraph, ReverseArcStaticGraph, ReverseArcStaticGraph, StaticGraph, StaticGraph, StaticGraph, StaticGraph, StaticGraph> GraphType; TYPED_TEST_SUITE(GenericGraphInterfaceTest, GraphType); TYPED_TEST(GenericGraphInterfaceTest, EmptyGraph) { using NodeIndex = typename TypeParam::NodeIndex; using ArcIndex = typename TypeParam::ArcIndex; TypeParam graph; graph.Build(); EXPECT_EQ(NodeIndex(0), graph.num_nodes()); EXPECT_EQ(NodeIndex(0), graph.size()); EXPECT_EQ(ArcIndex(0), graph.num_arcs()); } TYPED_TEST(GenericGraphInterfaceTest, EmptyGraphAlternateSyntax) { using NodeIndex = typename TypeParam::NodeIndex; using ArcIndex = typename TypeParam::ArcIndex; TypeParam graph(NodeIndex(0), ArcIndex(0)); graph.Build(); EXPECT_EQ(NodeIndex(0), graph.num_nodes()); EXPECT_EQ(NodeIndex(0), graph.size()); EXPECT_EQ(ArcIndex(0), graph.num_arcs()); } TYPED_TEST(GenericGraphInterfaceTest, GraphWithNodesButNoArc) { using NodeIndex = typename TypeParam::NodeIndex; using ArcIndex = typename TypeParam::ArcIndex; const NodeIndex kNodes(1000); TypeParam graph(kNodes, ArcIndex(0)); graph.Build(); EXPECT_EQ(kNodes, graph.num_nodes()); EXPECT_EQ(kNodes, graph.size()); EXPECT_EQ(ArcIndex(0), graph.num_arcs()); int count = 0; for (const NodeIndex node : graph.AllNodes()) { for ([[maybe_unused]] const ArcIndex arc : graph.OutgoingArcs(node)) { ++count; } } EXPECT_EQ(0, count); for ([[maybe_unused]] const ArcIndex arc : graph.AllForwardArcs()) { ++count; } EXPECT_EQ(0, count); } TYPED_TEST(GenericGraphInterfaceTest, BuildWithRandomArc) { using NodeIndex = typename TypeParam::NodeIndex; using ArcIndex = typename TypeParam::ArcIndex; const int kNodes = 1000; const int kArcs = 5 * kNodes; std::vector heads(kArcs); std::vector tails(kArcs); std::mt19937 randomizer(42); for (int i = 0; i < kArcs; ++i) { heads[i] = NodeIndex(absl::Uniform(randomizer, 0, kNodes)); tails[i] = NodeIndex(absl::Uniform(randomizer, 0, kNodes)); } for (int i = 0; i < 4; ++i) { const bool reserve = i % 2; const bool test_permutation = i < 2; ConstructAndCheckGraph(NodeIndex(kNodes), ArcIndex(kArcs), heads, tails, reserve, test_permutation); } } // This exercise the arc index permutation a bit differently, it also // test for node with 0 outgoing arcs. TYPED_TEST(GenericGraphInterfaceTest, BuildWithOrderedArc) { using NodeIndex = typename TypeParam::NodeIndex; using ArcIndex = typename TypeParam::ArcIndex; const int kNodes = 10000; const int kDegree = 2; const int kArcs = kDegree * kNodes; std::vector heads(kArcs); std::vector tails(kArcs); std::mt19937 randomizer(42); int index = 0; for (int i = 0; i < kNodes; ++i) { for (int j = 0; j < kDegree; ++j) { heads[index] = NodeIndex(absl::Uniform(randomizer, 0, kNodes)); tails[index] = NodeIndex(i); index++; } } for (int i = 0; i < 4; ++i) { const bool reserve = i % 2; const bool test_permutation = i < 2; ConstructAndCheckGraph(NodeIndex(kNodes), ArcIndex(kArcs), heads, tails, reserve, test_permutation); } } TYPED_TEST(GenericGraphInterfaceTest, PastTheEndIterators) { using NodeIndex = typename TypeParam::NodeIndex; TypeParam graph; graph.AddArc(NodeIndex(0), NodeIndex(1)); graph.AddArc(NodeIndex(0), NodeIndex(2)); graph.AddArc(NodeIndex(0), NodeIndex(3)); graph.AddArc(NodeIndex(3), NodeIndex(4)); graph.AddArc(NodeIndex(1), NodeIndex(4)); graph.Build(); for (NodeIndex i(0); i < NodeIndex(4); ++i) { EXPECT_EQ(graph.OutgoingArcsStartingFrom(i, TypeParam::kNilArc).end(), graph.OutgoingArcs(i).end()); if constexpr (TypeParam::kHasNegativeReverseArcs) { EXPECT_EQ(graph.IncomingArcsStartingFrom(i, TypeParam::kNilArc).end(), graph.IncomingArcs(i).end()); EXPECT_EQ( graph.OppositeIncomingArcsStartingFrom(i, TypeParam::kNilArc).end(), graph.OppositeIncomingArcs(i).end()); EXPECT_EQ( graph .OutgoingOrOppositeIncomingArcsStartingFrom(i, TypeParam::kNilArc) .end(), typename TypeParam::OutgoingOrOppositeIncomingArcIterator( graph, i, TypeParam::kNilArc)); } } } TEST(StaticGraphTest, HeadAndTailBeforeAndAfterBuild) { for (const bool poll_in_the_middle_of_construction : {false, true}) { for (const bool build : {false, true}) { SCOPED_TRACE(absl::StrCat( "Polling in the middle of construction: ", poll_in_the_middle_of_construction, ", Calling Build() at the end of the construction: ", build)); StaticGraph<> graph; graph.AddArc(1, 3); graph.AddArc(2, 1); graph.AddArc(4, 6); if (poll_in_the_middle_of_construction) { ASSERT_EQ(1, graph.Tail(0)); ASSERT_EQ(3, graph.Head(0)); ASSERT_EQ(2, graph.Tail(1)); ASSERT_EQ(1, graph.Head(1)); ASSERT_EQ(4, graph.Tail(2)); ASSERT_EQ(6, graph.Head(2)); ASSERT_EQ(3, graph.num_arcs()); } graph.AddArc(2, 1); graph.AddArc(0, 0); graph.AddArc(7, 7); if (build) { graph.Build(); std::vector arcs; for (int i = 0; i < graph.num_arcs(); ++i) { arcs.push_back(absl::StrCat(graph.Tail(i), "->", graph.Head(i))); } EXPECT_THAT(arcs, UnorderedElementsAre("1->3", "2->1", "4->6", "2->1", "0->0", "7->7")); } else { ASSERT_EQ(1, graph.Tail(0)); ASSERT_EQ(3, graph.Head(0)); ASSERT_EQ(2, graph.Tail(1)); ASSERT_EQ(1, graph.Head(1)); ASSERT_EQ(4, graph.Tail(2)); ASSERT_EQ(6, graph.Head(2)); ASSERT_EQ(2, graph.Tail(3)); ASSERT_EQ(1, graph.Head(3)); ASSERT_EQ(0, graph.Tail(4)); ASSERT_EQ(0, graph.Head(4)); ASSERT_EQ(7, graph.Tail(5)); ASSERT_EQ(7, graph.Head(5)); ASSERT_EQ(6, graph.num_arcs()); } } } } TEST(StaticGraphTest, FromArcs) { const std::vector> arcs = {{1, 2}, {1, 0}}; StaticGraph<> graph = StaticGraph<>::FromArcs(3, arcs); EXPECT_EQ(3, graph.num_nodes()); EXPECT_EQ(3, graph.size()); EXPECT_EQ(2, graph.num_arcs()); std::vector> read_arcs; for (const auto arc : graph.AllForwardArcs()) { read_arcs.push_back({graph.Tail(arc), graph.Head(arc)}); } EXPECT_THAT(read_arcs, UnorderedElementsAre(Pair(1, 2), Pair(1, 0))); } TEST(CompleteGraphTest, EmptyGraph) { CompleteGraph<> graph(0); EXPECT_EQ(0, graph.num_nodes()); EXPECT_EQ(0, graph.size()); EXPECT_EQ(0, graph.num_arcs()); for (const auto arc : graph.AllForwardArcs()) { EXPECT_TRUE(false); EXPECT_TRUE(graph.IsArcValid(arc)); } } TEST(CompleteGraphTest, OneNodeGraph) { CompleteGraph<> graph(1); EXPECT_EQ(1, graph.num_nodes()); EXPECT_EQ(1, graph.size()); EXPECT_EQ(1, graph.num_arcs()); EXPECT_EQ(graph.Head(0), 0); EXPECT_EQ(graph.Tail(0), 0); } TEST(CompleteGraphTest, NonEmptyGraph) { static const int kNumNodes = 5; CompleteGraph<> graph(kNumNodes); EXPECT_EQ(kNumNodes, graph.num_nodes()); EXPECT_EQ(kNumNodes, graph.size()); EXPECT_EQ(kNumNodes * kNumNodes, graph.num_arcs()); int count = 0; for (const auto arc : graph.AllForwardArcs()) { ++count; EXPECT_TRUE(graph.IsArcValid(arc)); } EXPECT_EQ(kNumNodes * kNumNodes, count); for (const auto node : graph.AllNodes()) { EXPECT_EQ(kNumNodes, graph.OutDegree(node)); EXPECT_TRUE(graph.IsNodeValid(node)); count = 0; for (const auto arc : graph.OutgoingArcs(node)) { EXPECT_EQ(node, graph.Tail(arc)); ++count; EXPECT_EQ(*(graph.OutgoingArcsStartingFrom(node, arc).begin()), arc); } EXPECT_EQ(kNumNodes, count); count = 0; for (const auto head : graph[node]) { ++count; EXPECT_TRUE(graph.IsNodeValid(head)); } EXPECT_EQ(kNumNodes, count); } } TEST(CompleteBipartiteGraphTest, EmptyGraph) { CompleteBipartiteGraph<> graph(0, 0); EXPECT_EQ(0, graph.num_nodes()); EXPECT_EQ(0, graph.size()); EXPECT_EQ(0, graph.num_arcs()); EXPECT_TRUE(graph.AllForwardArcs().empty()); } TEST(CompleteBipartiteGraphTest, OneRightNodeGraph) { CompleteBipartiteGraph<> graph(3, 1); EXPECT_EQ(4, graph.num_nodes()); EXPECT_EQ(4, graph.size()); EXPECT_EQ(3, graph.num_arcs()); EXPECT_EQ(graph.Head(0), 3); EXPECT_EQ(graph.Head(1), 3); EXPECT_EQ(graph.Head(2), 3); EXPECT_EQ(graph.Tail(0), 0); EXPECT_EQ(graph.Tail(1), 1); EXPECT_EQ(graph.Tail(2), 2); } TEST(CompleteBipartiteGraphTest, NonEmptyGraph) { static const int kNumRightNodes = 5; static const int kNumLeftNodes = 3; CompleteBipartiteGraph<> graph(kNumLeftNodes, kNumRightNodes); EXPECT_EQ(kNumLeftNodes + kNumRightNodes, graph.num_nodes()); EXPECT_EQ(graph.num_nodes(), graph.size()); EXPECT_EQ(kNumLeftNodes * kNumRightNodes, graph.num_arcs()); int count = 0; for (const auto arc : graph.AllForwardArcs()) { ++count; EXPECT_TRUE(graph.IsArcValid(arc)); } EXPECT_EQ(kNumLeftNodes * kNumRightNodes, count); for (const auto node : graph.AllNodes()) { EXPECT_EQ(node < kNumLeftNodes ? kNumRightNodes : 0, graph.OutDegree(node)); EXPECT_TRUE(graph.IsNodeValid(node)); count = 0; for (const auto arc : graph.OutgoingArcs(node)) { EXPECT_EQ(node, graph.Tail(arc)); EXPECT_EQ(kNumLeftNodes + count, graph.Head(arc)); ++count; EXPECT_EQ(*(graph.OutgoingArcsStartingFrom(node, arc).begin()), arc); } EXPECT_EQ(node < kNumLeftNodes ? kNumRightNodes : 0, count); count = 0; for (const auto head : graph[node]) { EXPECT_EQ(head, kNumLeftNodes + count); EXPECT_TRUE(graph.IsNodeValid(head)); ++count; } EXPECT_EQ(node < kNumLeftNodes ? kNumRightNodes : 0, count); } for (const auto arc : graph.AllForwardArcs()) { EXPECT_EQ(graph.GetArc(graph.Tail(arc), graph.Head(arc)), arc); } } TEST(CompleteBipartiteGraphTest, Overflow) { using NodeIndex = uint32_t; using ArcIndex = uint64_t; using Graph = CompleteBipartiteGraph; constexpr NodeIndex kNumNodes = std::numeric_limits::max() / 2; Graph graph(kNumNodes, kNumNodes); EXPECT_EQ(2 * kNumNodes, graph.num_nodes()); EXPECT_EQ(graph.num_nodes(), graph.size()); EXPECT_EQ(kNumNodes * kNumNodes, graph.num_arcs()); constexpr uint64_t kLeft = kNumNodes - 3; constexpr uint64_t kRight = kNumNodes + kNumNodes - 2; EXPECT_EQ(graph.GetArc(kLeft, kRight), kLeft * kNumNodes + (kRight - kNumNodes)); } TEST(SVector, NoHeapCheckerFalsePositive) { static const internal::SVector* const kVector = []() { auto* vector = new internal::SVector(); vector->resize(5000); return vector; }(); EXPECT_EQ(kVector->size(), 5000); } template static void BM_RandomArcs(benchmark::State& state) { const int kRandomSeed = 0; const int kNodes = 10 * 1000 * 1000; const int kArcs = 5 * kNodes; int items_processed = 0; for (auto _ : state) { GraphType graph; if (reserve) { graph.Reserve(kNodes, kArcs); } std::mt19937 randomizer(kRandomSeed); for (int i = 0; i < kArcs; ++i) { graph.AddArc(absl::Uniform(randomizer, 0, kNodes), absl::Uniform(randomizer, 0, kNodes)); } graph.Build(); items_processed += kArcs; } state.SetItemsProcessed(items_processed); } template static void BM_OrderedArcs(benchmark::State& state) { const int kRandomSeed = 0; const int kNodes = 10 * 1000 * 1000; const int kDegree = 5; const int kArcs = kDegree * kNodes; int items_processed = 0; for (auto _ : state) { GraphType graph; if (reserve) { graph.Reserve(kNodes, kArcs); } std::mt19937 randomizer(kRandomSeed); for (int i = 0; i < kNodes; ++i) { for (int j = 0; j < kDegree; ++j) { graph.AddArc(i, absl::Uniform(randomizer, 0, kNodes)); } } graph.Build(); items_processed += kArcs; } state.SetItemsProcessed(items_processed); } // This is just here to get some timing on the AddArc() function to see how the // GraphType building time is split between the AddArc() calls and the actual // Build() call. It is not usefull for all type of graph. template static void BM_RandomArcsBeforeBuild(benchmark::State& state) { const int kRandomSeed = 0; const int kNodes = 10 * 1000 * 1000; const int kArcs = 5 * kNodes; int items_processed = 0; for (auto _ : state) { GraphType graph; if (reserve) { graph.Reserve(kNodes, kArcs); } std::mt19937 randomizer(kRandomSeed); for (int i = 0; i < kArcs; ++i) { graph.AddArc(absl::Uniform(randomizer, 0, kNodes), absl::Uniform(randomizer, 0, kNodes)); } items_processed += kArcs; } state.SetItemsProcessed(items_processed); } // A basic vector> graph implementation that many people uses. It is // quite slower and use more memory than a static graph, except maybe during // construction. class VectorVectorGraph { public: VectorVectorGraph() {} void Reserve(int32_t num_nodes, int32_t num_arcs) { // We could only reserve the space, but AddArc() need to be smarter then. graph_.resize(num_nodes); } void Build() {} void AddArc(int32_t tail, int32_t head) { graph_[tail].push_back(head); } private: std::vector> graph_; }; #define RESERVE true #define NO_RESERVE false BENCHMARK_TEMPLATE2(BM_RandomArcsBeforeBuild, StaticGraph<>, RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcsBeforeBuild, StaticGraph<>, NO_RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcsBeforeBuild, ReverseArcStaticGraph<>, RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcsBeforeBuild, ReverseArcStaticGraph<>, NO_RESERVE); BENCHMARK_TEMPLATE2(BM_OrderedArcs, ListGraph<>, RESERVE); BENCHMARK_TEMPLATE2(BM_OrderedArcs, ListGraph<>, NO_RESERVE); BENCHMARK_TEMPLATE2(BM_OrderedArcs, StaticGraph<>, RESERVE); BENCHMARK_TEMPLATE2(BM_OrderedArcs, StaticGraph<>, NO_RESERVE); BENCHMARK_TEMPLATE2(BM_OrderedArcs, VectorVectorGraph, RESERVE); BENCHMARK_TEMPLATE2(BM_OrderedArcs, ReverseArcListGraph<>, RESERVE); BENCHMARK_TEMPLATE2(BM_OrderedArcs, ReverseArcListGraph<>, NO_RESERVE); BENCHMARK_TEMPLATE2(BM_OrderedArcs, ReverseArcStaticGraph<>, RESERVE); BENCHMARK_TEMPLATE2(BM_OrderedArcs, ReverseArcStaticGraph<>, NO_RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcs, ListGraph<>, RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcs, ListGraph<>, NO_RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcs, StaticGraph<>, RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcs, StaticGraph<>, NO_RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcs, VectorVectorGraph, RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcs, ReverseArcListGraph<>, RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcs, ReverseArcListGraph<>, NO_RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcs, ReverseArcStaticGraph<>, RESERVE); BENCHMARK_TEMPLATE2(BM_RandomArcs, ReverseArcStaticGraph<>, NO_RESERVE); #undef RESERVE #undef NO_RESERVE template void BuildGraphForIterationsBenchmarks(GraphType* graph) { const int kRandomSeed = 0; const int kNodes = 10 * 1000 * 1000; const int kArcs = 5 * kNodes; graph->Reserve(kNodes, kArcs); std::mt19937 randomizer(kRandomSeed); for (int i = 0; i < kArcs; ++i) { graph->AddArc(absl::Uniform(randomizer, 0, kNodes), absl::Uniform(randomizer, 0, kNodes)); } graph->Build(); } template static void BM_OutgoingIterations(benchmark::State& state) { GraphType graph; BuildGraphForIterationsBenchmarks(&graph); int64_t num_arcs = 0; int64_t some_work = 0; for (auto _ : state) { for (int node = 0; node < graph.num_nodes(); ++node) { for (const int arc : graph.OutgoingArcs(node)) { some_work += graph.Head(arc); ++num_arcs; } } } CHECK_GT(some_work, 0); state.SetItemsProcessed(num_arcs); } BENCHMARK_TEMPLATE(BM_OutgoingIterations, ListGraph<>); BENCHMARK_TEMPLATE(BM_OutgoingIterations, StaticGraph<>); BENCHMARK_TEMPLATE(BM_OutgoingIterations, ReverseArcListGraph<>); BENCHMARK_TEMPLATE(BM_OutgoingIterations, ReverseArcStaticGraph<>); template static void BM_IncomingIterations(benchmark::State& state) { GraphType graph; BuildGraphForIterationsBenchmarks(&graph); int64_t num_arcs = 0; int64_t some_work = 0; for (auto _ : state) { for (int node = 0; node < graph.num_nodes(); ++node) { for (const int arc : graph.IncomingArcs(node)) { some_work += graph.Tail(arc); ++num_arcs; } } } CHECK_GT(some_work, 0); state.SetItemsProcessed(num_arcs); } BENCHMARK_TEMPLATE(BM_IncomingIterations, ReverseArcListGraph<>); BENCHMARK_TEMPLATE(BM_IncomingIterations, ReverseArcStaticGraph<>); template static void BM_OppositeIncomingIterations(benchmark::State& state) { GraphType graph; BuildGraphForIterationsBenchmarks(&graph); int64_t num_arcs = 0; int64_t some_work = 0; for (auto _ : state) { for (int node = 0; node < graph.num_nodes(); ++node) { for (const int arc : graph.OppositeIncomingArcs(node)) { some_work += graph.Head(arc); ++num_arcs; } } } CHECK_GT(some_work, 0); state.SetItemsProcessed(num_arcs); } BENCHMARK_TEMPLATE(BM_OppositeIncomingIterations, ReverseArcListGraph<>); BENCHMARK_TEMPLATE(BM_OppositeIncomingIterations, ReverseArcStaticGraph<>); template static void BM_OutgoingOrOppositeIncomingIterations(benchmark::State& state) { GraphType graph; BuildGraphForIterationsBenchmarks(&graph); int64_t num_arcs = 0; for (auto _ : state) { for (int node = 0; node < graph.num_nodes(); ++node) { for (const int arc : graph.OutgoingOrOppositeIncomingArcs(node)) { auto head = graph.Head(arc); benchmark::DoNotOptimize(head); } } } state.SetItemsProcessed(state.iterations() * num_arcs); } BENCHMARK_TEMPLATE(BM_OutgoingOrOppositeIncomingIterations, ReverseArcListGraph<>); BENCHMARK_TEMPLATE(BM_OutgoingOrOppositeIncomingIterations, ReverseArcStaticGraph<>); // It's bit sad, but having two loops to iterate over opposite incoming and // outgoing arcs is much faster than using `OutgoingOrOppositeIncomingArcs`. template static void BM_OutgoingOrOppositeIncomingIterationsTwoLoops( benchmark::State& state) { GraphType graph; BuildGraphForIterationsBenchmarks(&graph); for (auto _ : state) { for (int node = 0; node < graph.num_nodes(); ++node) { const auto work = [&graph](int arc) { auto head = graph.Head(arc); benchmark::DoNotOptimize(head); }; for (const int arc : graph.OppositeIncomingArcs(node)) { work(arc); } for (const int arc : graph.OutgoingArcs(node)) { work(arc); } } } state.SetItemsProcessed(state.iterations() * graph.num_arcs()); } BENCHMARK_TEMPLATE(BM_OutgoingOrOppositeIncomingIterationsTwoLoops, ReverseArcListGraph<>); BENCHMARK_TEMPLATE(BM_OutgoingOrOppositeIncomingIterationsTwoLoops, ReverseArcStaticGraph<>); template static void BM_IntegralTypeCopy(benchmark::State& state) { GraphType graph; BuildGraphForIterationsBenchmarks(&graph); for (auto s : state) { GraphType copied; benchmark::DoNotOptimize(copied = GraphType(graph)); } } BENCHMARK_TEMPLATE(BM_IntegralTypeCopy, ListGraph<>); BENCHMARK_TEMPLATE(BM_IntegralTypeCopy, StaticGraph<>); BENCHMARK_TEMPLATE(BM_IntegralTypeCopy, ReverseArcListGraph<>); BENCHMARK_TEMPLATE(BM_IntegralTypeCopy, ReverseArcStaticGraph<>); template void TailHeadBenchmark(benchmark::State& state, const GraphType& graph) { // Prevent constant folding. Weird construct due to b/284459966. auto* graph_ptr = &graph; benchmark::DoNotOptimize(graph_ptr); for (auto s : state) { const auto num_arcs = graph.num_arcs(); for (int arc = 0; arc < num_arcs; ++arc) { auto head = graph.Head(arc); auto tail = graph.Tail(arc); benchmark::DoNotOptimize(head); benchmark::DoNotOptimize(tail); } } } template static void BM_CompleteGraphTailHead(benchmark::State& state) { constexpr int kNumNodes = 100; CompleteGraph graph(kNumNodes); TailHeadBenchmark(state, graph); } BENCHMARK_TEMPLATE(BM_CompleteGraphTailHead, int32_t); BENCHMARK_TEMPLATE(BM_CompleteGraphTailHead, int16_t); template static void BM_CompleteBipartiteGraphTailHead(benchmark::State& state) { constexpr int kNumLeft = 100; CompleteBipartiteGraph graph(kNumLeft, kNumLeft); TailHeadBenchmark(state, graph); } BENCHMARK_TEMPLATE(BM_CompleteBipartiteGraphTailHead, int32_t); BENCHMARK_TEMPLATE(BM_CompleteBipartiteGraphTailHead, int16_t); } // namespace util