Skip to content

Commit cf4e993

Browse files
committed
graph: Extract Build() from the BaseGraph class.
* Add a BaseGraph::Builder class that simply forwards mutable calls (AddNode AddArc, Reserve, Build) to the underlying class. * Update clients that use XXXStaticGraph, or a template graph to use Builder. Users who directly use ListGraph do not need to be changed if they do not call Build. If they do, remove the unnecessery call to Build. * Move mutable functions from BaseGraph to XXXGraph::Builder.
1 parent 084b2db commit cf4e993

57 files changed

Lines changed: 1341 additions & 1931 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ortools/algorithms/find_graph_symmetries_test.cc

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -696,22 +696,20 @@ TEST_F(FindSymmetriesTest, InwardGrid) {
696696
}
697697
}
698698

699-
void AddReverseArcs(Graph* graph) {
699+
void AddReverseArcs(Graph::Builder& builder) {
700+
const auto graph = Graph::Builder(builder).Build(nullptr);
700701
const int num_arcs = graph->num_arcs();
701702
for (int a = 0; a < num_arcs; ++a) {
702-
graph->AddArc(graph->Head(a), graph->Tail(a));
703+
builder.AddArc(graph->Head(a), graph->Tail(a));
703704
}
704705
}
705706

706-
void AddReverseArcsAndFinalize(Graph* graph) {
707-
AddReverseArcs(graph);
708-
graph->Build();
709-
}
710-
711-
void SetGraphEdges(absl::Span<const std::pair<int, int>> edges, Graph* graph) {
712-
DCHECK_EQ(graph->num_arcs(), 0);
713-
for (const auto [from, to] : edges) graph->AddArc(from, to);
714-
AddReverseArcsAndFinalize(graph);
707+
std::unique_ptr<Graph> MakeGraphFromEdges(
708+
absl::Span<const std::pair<int, int>> edges) {
709+
Graph::Builder builder;
710+
for (const auto [from, to] : edges) builder.AddArc(from, to);
711+
AddReverseArcs(builder);
712+
return std::move(builder).Build(nullptr);
715713
}
716714

717715
TEST(CountTrianglesTest, EmptyGraph) {
@@ -723,19 +721,18 @@ TEST(CountTrianglesTest, SimpleUndirectedExample) {
723721
// 0--1--2
724722
// `.|`.|
725723
// 3--4--5
726-
Graph g;
727-
SetGraphEdges(
728-
{{0, 1}, {1, 2}, {0, 3}, {1, 4}, {1, 3}, {2, 4}, {3, 4}, {4, 5}}, &g);
724+
const auto g = MakeGraphFromEdges(
725+
{{0, 1}, {1, 2}, {0, 3}, {1, 4}, {1, 3}, {2, 4}, {3, 4}, {4, 5}});
729726
// Reminder: every undirected triangle counts as two directed triangles.
730-
EXPECT_THAT(CountTriangles(g, /*max_degree=*/999),
727+
EXPECT_THAT(CountTriangles(*g, /*max_degree=*/999),
731728
ElementsAre(2, 6, 2, 4, 4, 0));
732-
EXPECT_THAT(CountTriangles(g, /*max_degree=*/3),
729+
EXPECT_THAT(CountTriangles(*g, /*max_degree=*/3),
733730
ElementsAre(2, 0, 2, 4, 0, 0));
734-
EXPECT_THAT(CountTriangles(g, /*max_degree=*/2),
731+
EXPECT_THAT(CountTriangles(*g, /*max_degree=*/2),
735732
ElementsAre(2, 0, 2, 0, 0, 0));
736-
EXPECT_THAT(CountTriangles(g, /*max_degree=*/1),
733+
EXPECT_THAT(CountTriangles(*g, /*max_degree=*/1),
737734
ElementsAre(0, 0, 0, 0, 0, 0));
738-
EXPECT_THAT(CountTriangles(g, /*max_degree=*/0),
735+
EXPECT_THAT(CountTriangles(*g, /*max_degree=*/0),
739736
ElementsAre(0, 0, 0, 0, 0, 0));
740737
}
741738

@@ -745,7 +742,7 @@ TEST(CountTrianglesTest, SimpleDirectedExample) {
745742
// 0 | | 5
746743
// \ | v /
747744
// `-> 3 <- 4 <-'
748-
Graph g;
745+
Graph::Builder builder;
749746
for (auto [from, to] : std::vector<std::pair<int, int>>{
750747
{0, 1},
751748
{1, 2},
@@ -757,28 +754,27 @@ TEST(CountTrianglesTest, SimpleDirectedExample) {
757754
{2, 4},
758755
{4, 2},
759756
}) {
760-
g.AddArc(from, to);
757+
builder.AddArc(from, to);
761758
}
762-
g.Build();
763-
EXPECT_THAT(CountTriangles(g, /*max_degree=*/999),
759+
const auto g = std::move(builder).Build(nullptr);
760+
EXPECT_THAT(CountTriangles(*g, /*max_degree=*/999),
764761
ElementsAre(1, 0, 0, 0, 0, 2));
765-
EXPECT_THAT(CountTriangles(g, /*max_degree=*/1),
762+
EXPECT_THAT(CountTriangles(*g, /*max_degree=*/1),
766763
ElementsAre(0, 0, 0, 0, 0, 0));
767764
}
768765

769766
TEST(LocalBfsTest, SimpleExample) {
770767
// 0--1--2
771768
// `.|`.|
772769
// 3--4--5
773-
Graph g;
774-
SetGraphEdges(
775-
{{0, 1}, {1, 2}, {0, 3}, {1, 4}, {1, 3}, {2, 4}, {3, 4}, {4, 5}}, &g);
776-
std::vector<bool> tmp_mask(g.num_nodes(), false);
770+
const auto g = MakeGraphFromEdges(
771+
{{0, 1}, {1, 2}, {0, 3}, {1, 4}, {1, 3}, {2, 4}, {3, 4}, {4, 5}});
772+
std::vector<bool> tmp_mask(g->num_nodes(), false);
777773
std::vector<int> visited;
778774
std::vector<int> num_within_radius;
779775

780776
// Run a first unlimited BFS from 0.
781-
LocalBfs(g, /*source=*/0, /*stop_after_num_nodes=*/99, &visited,
777+
LocalBfs(*g, /*source=*/0, /*stop_after_num_nodes=*/99, &visited,
782778
&num_within_radius, &tmp_mask);
783779
EXPECT_THAT(
784780
visited,
@@ -791,28 +787,28 @@ TEST(LocalBfsTest, SimpleExample) {
791787

792788
// Then a BFS that stops after visiting 4 nodes: we should finish exploring
793789
// that distance, i.e. explore 2 and 4, but not 5. Still, 5 is "visited".
794-
LocalBfs(g, /*source=*/0, /*stop_after_num_nodes=*/4, &visited,
790+
LocalBfs(*g, /*source=*/0, /*stop_after_num_nodes=*/4, &visited,
795791
&num_within_radius, &tmp_mask);
796792
EXPECT_THAT(visited, AnyOf(ElementsAre(0, 1, 3, 2, 4, 5),
797793
ElementsAre(0, 1, 3, 4, 2, 5),
798794
ElementsAre(0, 3, 1, 4, 2, 5)));
799795
EXPECT_THAT(num_within_radius, ElementsAre(1, 3, 5, 6));
800796

801797
// Then a BFS that stops after visiting 2 nodes.
802-
LocalBfs(g, /*source=*/0, /*stop_after_num_nodes=*/2, &visited,
798+
LocalBfs(*g, /*source=*/0, /*stop_after_num_nodes=*/2, &visited,
803799
&num_within_radius, &tmp_mask);
804800
EXPECT_THAT(visited,
805801
AnyOf(ElementsAre(0, 1, 3, 2, 4), ElementsAre(0, 1, 3, 4, 2),
806802
ElementsAre(0, 3, 1, 4, 2)));
807803
EXPECT_THAT(num_within_radius, ElementsAre(1, 3, 5));
808804

809805
// Now run a BFS from node 3, stop exploring after 1 node.
810-
LocalBfs(g, /*source=*/3, /*stop_after_num_nodes=*/1, &visited,
806+
LocalBfs(*g, /*source=*/3, /*stop_after_num_nodes=*/1, &visited,
811807
&num_within_radius, &tmp_mask);
812808
EXPECT_THAT(visited, UnorderedElementsAre(3, 0, 1, 4));
813809
EXPECT_THAT(num_within_radius, ElementsAre(1, 4));
814810
// Now after 2 nodes.
815-
LocalBfs(g, /*source=*/3, /*stop_after_num_nodes=*/2, &visited,
811+
LocalBfs(*g, /*source=*/3, /*stop_after_num_nodes=*/2, &visited,
816812
&num_within_radius, &tmp_mask);
817813
EXPECT_THAT(visited, UnorderedElementsAre(3, 0, 1, 4, 2, 5));
818814
EXPECT_THAT(num_within_radius, ElementsAre(1, 4, 6));

ortools/bop/boolean_problem.cc

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ class IdGenerator {
561561
// in [0, num_classes) and any symmetry will only map nodes with the same class
562562
// between each other.
563563
template <typename Graph>
564-
Graph* GenerateGraphForSymmetryDetection(
564+
std::unique_ptr<const Graph> GenerateGraphForSymmetryDetection(
565565
const LinearBooleanProblem& problem,
566566
std::vector<int>* initial_equivalence_classes) {
567567
// First, we convert the problem to its canonical representation.
@@ -578,7 +578,7 @@ Graph* GenerateGraphForSymmetryDetection(
578578

579579
// TODO(user): reserve the memory for the graph? not sure it is worthwhile
580580
// since it would require some linear scan of the problem though.
581-
Graph* graph = new Graph();
581+
typename Graph::Builder builder;
582582
initial_equivalence_classes->clear();
583583

584584
// We will construct a graph with 3 different types of node that must be
@@ -593,8 +593,8 @@ Graph* GenerateGraphForSymmetryDetection(
593593
// Note that the indices are in [0, 2 * num_variables) and in one to one
594594
// correspondence with the index representation of a literal.
595595
const Literal literal = Literal(BooleanVariable(i), true);
596-
graph->AddArc(literal.Index().value(), literal.NegatedIndex().value());
597-
graph->AddArc(literal.NegatedIndex().value(), literal.Index().value());
596+
builder.AddArc(literal.Index().value(), literal.NegatedIndex().value());
597+
builder.AddArc(literal.NegatedIndex().value(), literal.Index().value());
598598
}
599599

600600
// We use 0 for their initial equivalence class, but that may be modified
@@ -648,18 +648,18 @@ Graph* GenerateGraphForSymmetryDetection(
648648
// Connect this node to the constraint node. Note that we don't
649649
// technically need the arcs in both directions, but that may help a bit
650650
// the algorithm to find symmetries.
651-
graph->AddArc(constraint_node_index, current_node_index);
652-
graph->AddArc(current_node_index, constraint_node_index);
651+
builder.AddArc(constraint_node_index, current_node_index);
652+
builder.AddArc(current_node_index, constraint_node_index);
653653
}
654654

655655
// Connect this node to the associated term.literal node. Note that we
656656
// don't technically need the arcs in both directions, but that may help a
657657
// bit the algorithm to find symmetries.
658-
graph->AddArc(current_node_index, term.literal.Index().value());
659-
graph->AddArc(term.literal.Index().value(), current_node_index);
658+
builder.AddArc(current_node_index, term.literal.Index().value());
659+
builder.AddArc(term.literal.Index().value(), current_node_index);
660660
}
661661
}
662-
graph->Build();
662+
auto graph = std::move(builder).Build(nullptr);
663663
DCHECK_EQ(graph->num_nodes(), initial_equivalence_classes->size());
664664
return graph;
665665
}
@@ -704,8 +704,8 @@ void FindLinearBooleanProblemSymmetries(
704704
std::vector<std::unique_ptr<SparsePermutation>>* generators) {
705705
typedef GraphSymmetryFinder::Graph Graph;
706706
std::vector<int> equivalence_classes;
707-
std::unique_ptr<Graph> graph(
708-
GenerateGraphForSymmetryDetection<Graph>(problem, &equivalence_classes));
707+
std::unique_ptr<const Graph> graph =
708+
GenerateGraphForSymmetryDetection<Graph>(problem, &equivalence_classes);
709709
LOG(INFO) << "Graph has " << graph->num_nodes() << " nodes and "
710710
<< graph->num_arcs() / 2 << " edges.";
711711
#if defined(ORTOOLS_TARGET_OS_SUPPORTS_FILE)

ortools/graph/BUILD.bazel

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,9 @@ cc_test(
4848
"//ortools/base:types",
4949
"//ortools/graph_base:graph",
5050
"//ortools/graph_base:io",
51-
"//ortools/graph_base:test_util",
5251
"//ortools/util:flat_matrix",
53-
"@abseil-cpp//absl/log:check",
5452
"@abseil-cpp//absl/random",
5553
"@abseil-cpp//absl/random:distributions",
56-
"@google_benchmark//:benchmark",
5754
],
5855
)
5956

@@ -150,7 +147,6 @@ cc_test(
150147
"@abseil-cpp//absl/random:distributions",
151148
"@abseil-cpp//absl/strings",
152149
"@abseil-cpp//absl/types:span",
153-
"@google_benchmark//:benchmark",
154150
],
155151
)
156152

@@ -215,7 +211,6 @@ cc_test(
215211
"@abseil-cpp//absl/status",
216212
"@abseil-cpp//absl/strings:str_format",
217213
"@abseil-cpp//absl/types:span",
218-
"@google_benchmark//:benchmark",
219214
],
220215
)
221216

@@ -234,7 +229,6 @@ cc_test(
234229
"//ortools/base:gmock_main",
235230
"//ortools/graph_base:graph",
236231
"@abseil-cpp//absl/base:core_headers",
237-
"@google_benchmark//:benchmark",
238232
],
239233
)
240234

@@ -258,7 +252,6 @@ cc_test(
258252
"//ortools/base:types",
259253
"//ortools/graph_base:graph",
260254
"@abseil-cpp//absl/base:core_headers",
261-
"@abseil-cpp//absl/random:distributions",
262255
"@abseil-cpp//absl/types:span",
263256
"@google_benchmark//:benchmark",
264257
],
@@ -473,7 +466,6 @@ cc_test(
473466
"@abseil-cpp//absl/random:distributions",
474467
"@abseil-cpp//absl/strings:str_format",
475468
"@abseil-cpp//absl/types:span",
476-
"@google_benchmark//:benchmark",
477469
],
478470
)
479471

@@ -558,7 +550,6 @@ cc_test(
558550
"//ortools/graph_base:graph",
559551
"@abseil-cpp//absl/flags:flag",
560552
"@abseil-cpp//absl/log:check",
561-
"@abseil-cpp//absl/random:distributions",
562553
"@abseil-cpp//absl/types:span",
563554
"@google_benchmark//:benchmark",
564555
],
@@ -690,10 +681,7 @@ cc_test(
690681
"//ortools/base:gmock_main",
691682
"//ortools/graph_base:graph",
692683
"@abseil-cpp//absl/algorithm:container",
693-
"@abseil-cpp//absl/log:check",
694-
"@abseil-cpp//absl/random",
695684
"@abseil-cpp//absl/status",
696-
"@google_benchmark//:benchmark",
697685
],
698686
)
699687

@@ -715,7 +703,6 @@ cc_test(
715703
":minimum_vertex_cover",
716704
"//ortools/base:gmock_main",
717705
"@abseil-cpp//absl/algorithm:container",
718-
"@google_benchmark//:benchmark",
719706
],
720707
)
721708

@@ -754,6 +741,5 @@ cc_test(
754741
"@abseil-cpp//absl/random",
755742
"@abseil-cpp//absl/status",
756743
"@abseil-cpp//absl/types:span",
757-
"@google_benchmark//:benchmark",
758744
],
759745
)

ortools/graph/README.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ node.
158158
memory loads.
159159

160160
- `StaticGraph<>`: More memory and speed efficient than `ListGraph<>`, but
161-
requires calling `Build()` once after adding all nodes and arcs. Iterating
162-
outgoing arcs requires only `2` memory loads.
161+
requires building after adding all nodes and arcs. Iterating outgoing arcs
162+
requires only `2` memory loads.
163163

164164
- `ReverseArcListGraph<>` adds reverse arcs to `ListGraph<>`. Iterating
165165
neighboring arcs for a node requires `O(degree)` memory loads.
@@ -202,18 +202,18 @@ You can find some canonical examples in [`samples`][samples].
202202

203203
<!-- Links used throughout the document. -->
204204
[graph_h]: ../graph/graph.h
205-
[bounded_dijkstra_h]: http://google3/third_party/ortools/ortools/graph/bounded_dijkstra.h
206-
[bidirectional_dijkstra_h]: http://google3/third_party/ortools/ortools/graph/bidirectional_dijkstra.h
207-
[shortest_paths_h]: http://google3/third_party/ortools/ortools/graph/shortest_paths.h
208-
[dag_shortest_path_h]: http://google3/third_party/ortools/ortools/graph/dag_shortest_path.h
209-
[dag_constrained_shortest_path_h]: http://google3/third_party/ortools/ortools/graph/dag_constrained_shortest_path.h
210-
[hamiltonian_path_h]: http://google3/third_party/ortools/ortools/graph/hamiltonian_path.h
211-
[eulerian_path_h]: http://google3/third_party/ortools/ortools/graph/eulerian_path.h
212-
[connected_components_h]: http://google3/third_party/ortools/ortools/graph/connected_components.h
213-
[strongly_connected_components_h]: http://google3/third_party/ortools/ortools/graph/strongly_connected_components.h
214-
[cliques_h]: http://google3/third_party/ortools/ortools/graph/cliques.h
215-
[linear_assignment_h]: http://google3/third_party/ortools/ortools/graph/linear_assignment.h
216-
[max_flow_h]: http://google3/third_party/ortools/ortools/graph/max_flow.h
217-
[min_cost_flow_h]: http://google3/third_party/ortools/ortools/graph/min_cost_flow.h
218-
[samples]: http://google3/third_party/ortools/ortools/graph/samples/
205+
[bounded_dijkstra_h]: ../graph/bounded_dijkstra.h
206+
[bidirectional_dijkstra_h]: ../graph/bidirectional_dijkstra.h
207+
[shortest_paths_h]: ../graph/shortest_paths.h
208+
[dag_shortest_path_h]: ../graph/dag_shortest_path.h
209+
[dag_constrained_shortest_path_h]: ../graph/dag_constrained_shortest_path.h
210+
[hamiltonian_path_h]: ../graph/hamiltonian_path.h
211+
[eulerian_path_h]: ../graph/eulerian_path.h
212+
[connected_components_h]: ../graph/connected_components.h
213+
[strongly_connected_components_h]: ../graph/strongly_connected_components.h
214+
[cliques_h]: ../graph/cliques.h
215+
[linear_assignment_h]: ../graph/linear_assignment.h
216+
[max_flow_h]: ../graph/max_flow.h
217+
[min_cost_flow_h]: ../graph/min_cost_flow.h
218+
[samples]: ../graph/samples/
219219
[graph]: ../graph/

ortools/graph/assignment.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ SimpleLinearSumAssignment::Status SimpleLinearSumAssignment::Solve() {
6565
optimal_cost_ = 0;
6666
assignment_arcs_.clear();
6767
if (NumNodes() == 0) return OPTIMAL;
68+
6869
// HACK(user): Detect overflows early. In ./linear_assignment.h, the cost of
6970
// each arc is internally multiplied by cost_scaling_factor_ (which is equal
7071
// to (num_nodes + 1)) without overflow checking.
@@ -74,6 +75,11 @@ SimpleLinearSumAssignment::Status SimpleLinearSumAssignment::Solve() {
7475
if (unscaled_arc_cost > max_supported_arc_cost) return POSSIBLE_OVERFLOW;
7576
}
7677

78+
// Fast path if the graph is complete.
79+
if (const auto solve_satus = SolveIfCompleteBipartite()) {
80+
return *solve_satus;
81+
}
82+
7783
const ArcIndex num_arcs = arc_cost_.size();
7884
::util::ListGraph<> graph(2 * num_nodes_, num_arcs);
7985
LinearSumAssignment<::util::ListGraph<>, CostValue> assignment(graph,
@@ -93,4 +99,40 @@ SimpleLinearSumAssignment::Status SimpleLinearSumAssignment::Solve() {
9399
return OPTIMAL;
94100
}
95101

102+
std::optional<SimpleLinearSumAssignment::Status>
103+
SimpleLinearSumAssignment::SolveIfCompleteBipartite() {
104+
// Quick check: only do something if we have the correct number of arcs.
105+
const ArcIndex num_arcs = arc_cost_.size();
106+
if (static_cast<int64_t>(num_arcs) !=
107+
static_cast<int64_t>(num_nodes_) * num_nodes_) {
108+
return std::nullopt;
109+
}
110+
111+
// If there are no duplicate arcs, this is a complete bipartite graph!
112+
// Otherwise, we will fall back to the general case.
113+
::util::CompleteBipartiteGraph<> graph(num_nodes_, num_nodes_);
114+
std::vector<int> reverse_mapping(num_arcs, -1);
115+
LinearSumAssignment<::util::CompleteBipartiteGraph<>, CostValue> assignment(
116+
graph, num_nodes_);
117+
118+
for (ArcIndex user_index = 0; user_index < num_arcs; ++user_index) {
119+
const ::util::CompleteBipartiteGraph<>::ArcIndex new_index =
120+
graph.GetArc(arc_tail_[user_index], num_nodes_ + arc_head_[user_index]);
121+
122+
// Abort if we have duplicate arcs.
123+
if (reverse_mapping[new_index] != -1) return std::nullopt;
124+
reverse_mapping[new_index] = user_index;
125+
assignment.SetArcCost(new_index, arc_cost_[user_index]);
126+
}
127+
128+
if (!assignment.FinalizeSetup()) return POSSIBLE_OVERFLOW;
129+
if (!assignment.ComputeAssignment()) return INFEASIBLE;
130+
optimal_cost_ = assignment.GetCost();
131+
for (NodeIndex node = 0; node < num_nodes_; ++node) {
132+
assignment_arcs_.push_back(
133+
reverse_mapping[assignment.GetAssignmentArc(node)]);
134+
}
135+
return OPTIMAL;
136+
}
137+
96138
} // namespace operations_research

0 commit comments

Comments
 (0)