more graph cleaning

This commit is contained in:
Laurent Perron
2025-01-06 21:50:11 +01:00
parent 10b5ec6d0a
commit 2b734246ec
5 changed files with 186 additions and 135 deletions

View File

@@ -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"],

View File

@@ -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 <typename GraphType>
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

View File

@@ -17,13 +17,11 @@
#include <random>
#include <string>
#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 <typename GraphType>
class LineGraphDeathTest : public testing::Test {};
template <typename GraphType>
class LineGraphTest : public testing::Test {};
typedef testing::Types<StarGraph, ForwardStarGraph>
GraphTypesForLineGraphTesting;
TYPED_TEST_SUITE(LineGraphDeathTest, GraphTypesForLineGraphTesting);
TYPED_TEST_SUITE(LineGraphTest, GraphTypesForLineGraphTesting);
TYPED_TEST(LineGraphDeathTest, NullLineGraph) {
TypeParam graph;
#ifndef NDEBUG
EXPECT_DEATH(BuildLineGraph<TypeParam>(graph, nullptr),
"line_graph must not be NULL");
#else
EXPECT_FALSE(BuildLineGraph<TypeParam>(graph, nullptr));
#endif
}
TYPED_TEST(LineGraphDeathTest, NonEmptyLineGraph) {
TypeParam graph;
TypeParam line_graph(1, 1);
line_graph.AddArc(0, 0);
#ifndef NDEBUG
EXPECT_DEATH(BuildLineGraph<TypeParam>(graph, &line_graph),
"line_graph must be empty");
#else
EXPECT_FALSE(BuildLineGraph<TypeParam>(graph, &line_graph));
#endif
}
TYPED_TEST(LineGraphDeathTest, LineGraphOfEmptyGraph) {
TypeParam graph;
TypeParam line_graph;
EXPECT_TRUE(BuildLineGraph<TypeParam>(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<TypeParam>(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<TypeParam>(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 <typename GraphType, bool sort_arcs>
static void BM_RandomArcs(benchmark::State& state) {
const int kRandomSeed = 0;

View File

@@ -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 <typename GraphType>
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_

View File

@@ -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 <typename GraphType>
class LineGraphDeathTest : public testing::Test {};
template <typename GraphType>
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<TypeParam>(graph, nullptr),
"line_graph must not be NULL");
#else
EXPECT_FALSE(BuildLineGraph<TypeParam>(graph, nullptr));
#endif
}
TYPED_TEST(LineGraphDeathTest, NonEmptyLineGraph) {
TypeParam graph;
TypeParam line_graph(1, 1);
line_graph.AddArc(0, 0);
#ifndef NDEBUG
EXPECT_DEATH(BuildLineGraph<TypeParam>(graph, &line_graph),
"line_graph must be empty");
#else
EXPECT_FALSE(BuildLineGraph<TypeParam>(graph, &line_graph));
#endif
}
TYPED_TEST(LineGraphDeathTest, LineGraphOfEmptyGraph) {
TypeParam graph;
TypeParam line_graph;
EXPECT_TRUE(BuildLineGraph<TypeParam>(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<TypeParam>(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<TypeParam>(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