From 2b734246ece8d4f0c36631f48736495de3857d11 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Mon, 6 Jan 2025 21:50:11 +0100 Subject: [PATCH] more graph cleaning --- ortools/graph/BUILD.bazel | 19 +++++- ortools/graph/ebert_graph.h | 47 ------------- ortools/graph/ebert_graph_test.cc | 86 ------------------------ ortools/graph/line_graph.h | 62 +++++++++++++++++ ortools/graph/line_graph_test.cc | 107 ++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 135 deletions(-) create mode 100644 ortools/graph/line_graph.h create mode 100644 ortools/graph/line_graph_test.cc diff --git a/ortools/graph/BUILD.bazel b/ortools/graph/BUILD.bazel index a66cf2ed92..cb1bdf20ae 100644 --- a/ortools/graph/BUILD.bazel +++ b/ortools/graph/BUILD.bazel @@ -366,10 +366,8 @@ cc_test( srcs = ["ebert_graph_test.cc"], deps = [ ":ebert_graph", - "//ortools/base", "//ortools/base:gmock_main", "//ortools/util:permutation", - "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/random:distributions", "@com_google_absl//absl/strings", "@com_google_benchmark//:benchmark", @@ -617,6 +615,17 @@ cc_test( ], ) +cc_test( + name = "line_graph_test", + srcs = ["line_graph_test.cc"], + deps = [ + ":graph", + ":line_graph", + "//ortools/base:gmock_main", + "@com_google_absl//absl/base:core_headers", + ], +) + # Linear Assignment with full-featured interface and efficient # implementation. cc_library( @@ -717,6 +726,12 @@ cc_library( ], ) +cc_library( + name = "line_graph", + hdrs = ["line_graph.h"], + deps = ["@com_google_absl//absl/log"], +) + cc_test( name = "rooted_tree_test", srcs = ["rooted_tree_test.cc"], diff --git a/ortools/graph/ebert_graph.h b/ortools/graph/ebert_graph.h index 106d250f23..5311058eab 100644 --- a/ortools/graph/ebert_graph.h +++ b/ortools/graph/ebert_graph.h @@ -1718,53 +1718,6 @@ class AnnotatedGraphBuildManager num_nodes, num_arcs, sort_arcs) {} }; -// Builds a directed line graph for 'graph' (see "directed line graph" in -// http://en.wikipedia.org/wiki/Line_graph). Arcs of the original graph -// become nodes and the new graph contains only nodes created from arcs in the -// original graph (we use the notation (a->b) for these new nodes); the index -// of the node (a->b) in the new graph is exactly the same as the index of the -// arc a->b in the original graph. -// An arc from node (a->b) to node (c->d) in the new graph is added if and only -// if b == c in the original graph. -// This method expects that 'line_graph' is an empty graph (it has no nodes -// and no arcs). -// Returns false on an error. -template -bool BuildLineGraph(const GraphType& graph, GraphType* const line_graph) { - if (line_graph == nullptr) { - LOG(DFATAL) << "line_graph must not be NULL"; - return false; - } - if (line_graph->num_nodes() != 0) { - LOG(DFATAL) << "line_graph must be empty"; - return false; - } - typedef typename GraphType::ArcIterator ArcIterator; - typedef typename GraphType::OutgoingArcIterator OutgoingArcIterator; - // Sizing then filling. - typename GraphType::ArcIndex num_arcs = 0; - for (ArcIterator arc_iterator(graph); arc_iterator.Ok(); - arc_iterator.Next()) { - const typename GraphType::ArcIndex arc = arc_iterator.Index(); - const typename GraphType::NodeIndex head = graph.Head(arc); - for (OutgoingArcIterator iterator(graph, head); iterator.Ok(); - iterator.Next()) { - ++num_arcs; - } - } - line_graph->Reserve(graph.num_arcs(), num_arcs); - for (ArcIterator arc_iterator(graph); arc_iterator.Ok(); - arc_iterator.Next()) { - const typename GraphType::ArcIndex arc = arc_iterator.Index(); - const typename GraphType::NodeIndex head = graph.Head(arc); - for (OutgoingArcIterator iterator(graph, head); iterator.Ok(); - iterator.Next()) { - line_graph->AddArc(arc, iterator.Index()); - } - } - return true; -} - #undef DEFINE_STL_ITERATOR_FUNCTIONS } // namespace operations_research diff --git a/ortools/graph/ebert_graph_test.cc b/ortools/graph/ebert_graph_test.cc index 6a2ba5c6d9..a69785f02c 100644 --- a/ortools/graph/ebert_graph_test.cc +++ b/ortools/graph/ebert_graph_test.cc @@ -17,13 +17,11 @@ #include #include -#include "absl/base/macros.h" #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/base/macros.h" #include "ortools/util/permutation.h" namespace operations_research { @@ -1092,90 +1090,6 @@ TEST(ForwardEbertGraphTest, ImpossibleBuildTailArray) { EXPECT_FALSE(g.BuildTailArray()); } -// Testing line graph creation. - -// Empty fixture templates to collect the types of graphs on which we want to -// base the shortest paths template instances that we test. -template -class LineGraphDeathTest : public testing::Test {}; -template -class LineGraphTest : public testing::Test {}; - -typedef testing::Types - GraphTypesForLineGraphTesting; - -TYPED_TEST_SUITE(LineGraphDeathTest, GraphTypesForLineGraphTesting); -TYPED_TEST_SUITE(LineGraphTest, GraphTypesForLineGraphTesting); - -TYPED_TEST(LineGraphDeathTest, NullLineGraph) { - TypeParam graph; -#ifndef NDEBUG - EXPECT_DEATH(BuildLineGraph(graph, nullptr), - "line_graph must not be NULL"); -#else - EXPECT_FALSE(BuildLineGraph(graph, nullptr)); -#endif -} - -TYPED_TEST(LineGraphDeathTest, NonEmptyLineGraph) { - TypeParam graph; - TypeParam line_graph(1, 1); - line_graph.AddArc(0, 0); -#ifndef NDEBUG - EXPECT_DEATH(BuildLineGraph(graph, &line_graph), - "line_graph must be empty"); -#else - EXPECT_FALSE(BuildLineGraph(graph, &line_graph)); -#endif -} - -TYPED_TEST(LineGraphDeathTest, LineGraphOfEmptyGraph) { - TypeParam graph; - TypeParam line_graph; - EXPECT_TRUE(BuildLineGraph(graph, &line_graph)); - EXPECT_EQ(0, line_graph.num_nodes()); - EXPECT_EQ(0, line_graph.num_arcs()); -} - -TYPED_TEST(LineGraphTest, LineGraphOfSingleton) { - TypeParam graph(1, 1); - graph.AddArc(0, 0); - TypeParam line_graph; - EXPECT_TRUE(BuildLineGraph(graph, &line_graph)); - EXPECT_EQ(1, line_graph.num_nodes()); - EXPECT_EQ(1, line_graph.num_arcs()); -} - -TYPED_TEST(LineGraphTest, LineGraph) { - const NodeIndex kNodes = 4; - const ArcIndex kArcs[][2] = {{0, 1}, {0, 2}, {1, 2}, {2, 0}, {2, 3}}; - const ArcIndex kExpectedLineArcs[][2] = {{0, 2}, {2, 3}, {3, 0}, {3, 1}, - {2, 4}, {1, 4}, {1, 3}}; - TypeParam graph(kNodes, ABSL_ARRAYSIZE(kArcs)); - for (int i = 0; i < ABSL_ARRAYSIZE(kArcs); ++i) { - graph.AddArc(kArcs[i][0], kArcs[i][1]); - } - TypeParam line_graph; - EXPECT_TRUE(BuildLineGraph(graph, &line_graph)); - EXPECT_EQ(ABSL_ARRAYSIZE(kArcs), line_graph.num_nodes()); - EXPECT_EQ(ABSL_ARRAYSIZE(kExpectedLineArcs), line_graph.num_arcs()); - for (int i = 0; i < ABSL_ARRAYSIZE(kExpectedLineArcs); ++i) { - const NodeIndex expected_tail = kExpectedLineArcs[i][0]; - const NodeIndex expected_head = kExpectedLineArcs[i][1]; - bool found = false; - for (typename TypeParam::OutgoingArcIterator out_iterator(line_graph, - expected_tail); - out_iterator.Ok(); out_iterator.Next()) { - const ArcIndex arc = out_iterator.Index(); - if (line_graph.Head(arc) == expected_head) { - found = true; - break; - } - } - EXPECT_TRUE(found) << expected_tail << " " << expected_head; - } -} - template static void BM_RandomArcs(benchmark::State& state) { const int kRandomSeed = 0; diff --git a/ortools/graph/line_graph.h b/ortools/graph/line_graph.h new file mode 100644 index 0000000000..57ebac2341 --- /dev/null +++ b/ortools/graph/line_graph.h @@ -0,0 +1,62 @@ +// Copyright 2010-2024 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. + +#ifndef OR_TOOLS_GRAPH_LINE_GRAPH_H_ +#define OR_TOOLS_GRAPH_LINE_GRAPH_H_ + +#include "ortools/base/logging.h" + +namespace operations_research { + +// Builds a directed line graph for `graph` (see "directed line graph" in +// http://en.wikipedia.org/wiki/Line_graph). Arcs of the original graph +// become nodes and the new graph contains only nodes created from arcs in the +// original graph (we use the notation (a->b) for these new nodes); the index +// of the node (a->b) in the new graph is exactly the same as the index of the +// arc a->b in the original graph. +// An arc from node (a->b) to node (c->d) in the new graph is added if and only +// if b == c in the original graph. +// This method expects that `line_graph` is an empty graph (it has no nodes +// and no arcs). +// Returns false on an error. +template +bool BuildLineGraph(const GraphType& graph, GraphType* const line_graph) { + if (line_graph == nullptr) { + LOG(DFATAL) << "line_graph must not be NULL"; + return false; + } + if (line_graph->num_nodes() != 0) { + LOG(DFATAL) << "line_graph must be empty"; + return false; + } + // Sizing then filling. + using NodeIndex = typename GraphType::NodeIndex; + using ArcIndex = typename GraphType::ArcIndex; + ArcIndex num_arcs = 0; + for (const ArcIndex arc : graph.AllForwardArcs()) { + const NodeIndex head = graph.Head(arc); + num_arcs += graph.OutDegree(head); + } + line_graph->Reserve(graph.num_arcs(), num_arcs); + for (const ArcIndex arc : graph.AllForwardArcs()) { + const NodeIndex head = graph.Head(arc); + for (const ArcIndex outgoing_arc : graph.OutgoingArcs(head)) { + line_graph->AddArc(arc, outgoing_arc); + } + } + return true; +} + +} // namespace operations_research + +#endif // OR_TOOLS_GRAPH_LINE_GRAPH_H_ diff --git a/ortools/graph/line_graph_test.cc b/ortools/graph/line_graph_test.cc new file mode 100644 index 0000000000..2ffba4448c --- /dev/null +++ b/ortools/graph/line_graph_test.cc @@ -0,0 +1,107 @@ +// Copyright 2010-2024 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/line_graph.h" + +#include "absl/base/macros.h" +#include "gtest/gtest.h" +#include "ortools/graph/graph.h" + +namespace operations_research { +namespace { + +// Empty fixture templates to collect the types of graphs on which we want to +// base the shortest paths template instances that we test. +template +class LineGraphDeathTest : public testing::Test {}; +template +class LineGraphTest : public testing::Test {}; + +typedef testing::Types<::util::ListGraph<>, ::util::ReverseArcListGraph<>> + GraphTypesForLineGraphTesting; + +TYPED_TEST_SUITE(LineGraphDeathTest, GraphTypesForLineGraphTesting); +TYPED_TEST_SUITE(LineGraphTest, GraphTypesForLineGraphTesting); + +TYPED_TEST(LineGraphDeathTest, NullLineGraph) { + TypeParam graph; +#ifndef NDEBUG + EXPECT_DEATH(BuildLineGraph(graph, nullptr), + "line_graph must not be NULL"); +#else + EXPECT_FALSE(BuildLineGraph(graph, nullptr)); +#endif +} + +TYPED_TEST(LineGraphDeathTest, NonEmptyLineGraph) { + TypeParam graph; + TypeParam line_graph(1, 1); + line_graph.AddArc(0, 0); +#ifndef NDEBUG + EXPECT_DEATH(BuildLineGraph(graph, &line_graph), + "line_graph must be empty"); +#else + EXPECT_FALSE(BuildLineGraph(graph, &line_graph)); +#endif +} + +TYPED_TEST(LineGraphDeathTest, LineGraphOfEmptyGraph) { + TypeParam graph; + TypeParam line_graph; + EXPECT_TRUE(BuildLineGraph(graph, &line_graph)); + EXPECT_EQ(0, line_graph.num_nodes()); + EXPECT_EQ(0, line_graph.num_arcs()); +} + +TYPED_TEST(LineGraphTest, LineGraphOfSingleton) { + TypeParam graph(1, 1); + graph.AddArc(0, 0); + TypeParam line_graph; + EXPECT_TRUE(BuildLineGraph(graph, &line_graph)); + EXPECT_EQ(1, line_graph.num_nodes()); + EXPECT_EQ(1, line_graph.num_arcs()); +} + +TYPED_TEST(LineGraphTest, LineGraph) { + const typename TypeParam::NodeIndex kNodes = 4; + const typename TypeParam::ArcIndex kArcs[][2] = { + {0, 1}, {0, 2}, {1, 2}, {2, 0}, {2, 3}}; + const typename TypeParam::ArcIndex kExpectedLineArcs[][2] = { + {0, 2}, {2, 3}, {3, 0}, {3, 1}, {2, 4}, {1, 4}, {1, 3}}; + TypeParam graph(kNodes, ABSL_ARRAYSIZE(kArcs)); + for (int i = 0; i < ABSL_ARRAYSIZE(kArcs); ++i) { + graph.AddArc(kArcs[i][0], kArcs[i][1]); + } + TypeParam line_graph; + EXPECT_TRUE(BuildLineGraph(graph, &line_graph)); + EXPECT_EQ(ABSL_ARRAYSIZE(kArcs), line_graph.num_nodes()); + EXPECT_EQ(ABSL_ARRAYSIZE(kExpectedLineArcs), line_graph.num_arcs()); + for (int i = 0; i < ABSL_ARRAYSIZE(kExpectedLineArcs); ++i) { + const typename TypeParam::NodeIndex expected_tail = kExpectedLineArcs[i][0]; + const typename TypeParam::NodeIndex expected_head = kExpectedLineArcs[i][1]; + bool found = false; + for (typename TypeParam::OutgoingArcIterator out_iterator(line_graph, + expected_tail); + out_iterator.Ok(); out_iterator.Next()) { + const typename TypeParam::ArcIndex arc = out_iterator.Index(); + if (line_graph.Head(arc) == expected_head) { + found = true; + break; + } + } + EXPECT_TRUE(found) << expected_tail << " " << expected_head; + } +} + +} // namespace +} // namespace operations_research