Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
solve_flow_model.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14// This code loads flow-graph models (as Dimacs file or binary FlowModel proto)
15// and solves them with the OR-tools flow algorithms.
16//
17// Note(user): only min-cost flow is supported at this point.
18// TODO(user): move this DIMACS parser to its own class, like the ones in
19// routing/. This change would improve searchability of the parser.
20
21#include <algorithm>
22#include <cstdint>
23#include <cstdio>
24#include <cstdlib>
25#include <functional>
26#include <sstream>
27#include <string>
28#include <vector>
29
30#include "absl/base/log_severity.h"
31#include "absl/flags/flag.h"
32#include "absl/log/check.h"
33#include "absl/log/globals.h"
34#include "absl/log/log.h"
35#include "absl/strings/match.h"
36#include "absl/strings/str_format.h"
37#include "absl/strings/string_view.h"
42#include "ortools/base/path.h"
43#include "ortools/base/timer.h"
47#include "ortools/graph/graph.h"
51#include "ortools/util/stats.h"
52
53ABSL_FLAG(std::string, input, "", "Input file of the problem.");
54ABSL_FLAG(std::string, output_dimacs, "", "Output problem as a dimacs file.");
55ABSL_FLAG(std::string, output_proto, "", "Output problem as a flow proto.");
56ABSL_FLAG(bool, use_flow_graph, true, "Use special kind of graph.");
57ABSL_FLAG(bool, sort_heads, false, "Sort outgoing arcs by head.");
58ABSL_FLAG(bool, detect_reverse_arcs, true, "Detect reverse arcs.");
59
60namespace operations_research {
61
62// See http://lpsolve.sourceforge.net/5.5/DIMACS_mcf.htm for the dimacs file
63// format of a min cost flow problem.
64//
65// TODO(user): This currently only works for min cost flow problem.
67 std::string* dimacs) {
68 CHECK_EQ(FlowModelProto::MIN_COST_FLOW, flow_model.problem_type());
69 dimacs->clear();
70 dimacs->append("c Min-Cost flow problem generated from a FlowModelProto.\n");
71
72 // We need to compute the num_nodes from the nodes appearing in the arcs.
73 int64_t num_nodes = 0;
74 for (int64_t i = 0; i < flow_model.arcs_size(); ++i) {
75 num_nodes = std::max(num_nodes, flow_model.arcs(i).tail() + 1);
76 num_nodes = std::max(num_nodes, flow_model.arcs(i).head() + 1);
77 }
78
79 // Problem size and type.
80 const int64_t num_arcs = flow_model.arcs_size();
81 dimacs->append("c\nc Problem line (problem_type, num nodes, num arcs).\n");
82 dimacs->append(absl::StrFormat("p min %d %d\n", num_nodes, num_arcs));
83
84 // Nodes.
85 dimacs->append("c\nc Node descriptor lines (id, supply/demand).\n");
86 for (int64_t i = 0; i < flow_model.nodes_size(); ++i) {
87 const int64_t id = flow_model.nodes(i).id() + 1;
88 const int64_t supply = flow_model.nodes(i).supply();
89 if (supply != 0) {
90 dimacs->append(absl::StrFormat("n %d %d\n", id, supply));
91 }
92 }
93
94 // Arcs.
95 dimacs->append(
96 "c\nc Arc descriptor Lines (tail, head, minflow, maxflow, cost).\n");
97 for (int64_t i = 0; i < flow_model.arcs_size(); ++i) {
98 const int64_t tail = flow_model.arcs(i).tail() + 1;
99 const int64_t head = flow_model.arcs(i).head() + 1;
100 const int64_t unit_cost = flow_model.arcs(i).unit_cost();
101 const int64_t capacity = flow_model.arcs(i).capacity();
102 dimacs->append(absl::StrFormat("a %d %d %d %d %d\n", tail, head, int64_t{0},
103 capacity, unit_cost));
104 }
105 dimacs->append("c\n");
106}
107
108// Note(user): Going from Dimacs to flow adds an extra copy, but for now we
109// don't really care of the Dimacs file reading performance.
110// Returns true if the file was converted correctly.
111bool ConvertDimacsToFlowModel(absl::string_view file,
112 FlowModelProto* flow_model) {
113 flow_model->Clear();
114 FlowModelProto::ProblemType problem_type;
115 for (const std::string& line : FileLines(file)) {
116 if (line.empty()) continue;
117 if (line[0] == 'p') {
118 if (absl::StartsWith(line, "p min")) {
119 problem_type = FlowModelProto::MIN_COST_FLOW;
120 } else if (absl::StartsWith(line, "p max")) {
121 problem_type = FlowModelProto::MAX_FLOW;
122 } else {
123 LOG(ERROR) << "Unknown dimacs problem format.";
124 return false;
125 }
126 flow_model->set_problem_type(problem_type);
127 }
128 if (line[0] == 'n') {
129 int64_t id;
130 int64_t supply;
131 std::stringstream ss(line.substr(1));
132 switch (problem_type) {
134 ss >> id >> supply;
135 break;
137 std::string type;
138 ss >> id >> type;
139 supply = (type == "s" ? 1 : -1);
140 break;
141 }
142 default:
143 LOG(ERROR) << "Node line before the problem type definition.";
144 return false;
145 }
146 FlowNodeProto* node = flow_model->add_nodes();
147 node->set_id(id - 1);
148 node->set_supply(supply);
149 }
150 if (line[0] == 'a') {
151 int64_t tail;
152 int64_t head;
153 int64_t min_capacity = 0;
154 int64_t max_capacity = 0;
155 int64_t unit_cost = 0;
156 std::stringstream ss(line.substr(1));
157 switch (problem_type) {
159 ss >> tail >> head >> min_capacity >> max_capacity >> unit_cost;
160 break;
162 ss >> tail >> head >> max_capacity;
163 break;
164 default:
165 LOG(ERROR) << "Arc line before the problem type definition.";
166 return false;
167 }
168 FlowArcProto* arc = flow_model->add_arcs();
169 arc->set_tail(tail - 1);
170 arc->set_head(head - 1);
171 arc->set_capacity(max_capacity);
172 arc->set_unit_cost(unit_cost);
173 if (min_capacity != 0) {
174 LOG(ERROR) << "We do not support minimum capacity.";
175 return false;
176 }
177 }
178 }
179 return true;
180}
181
182// Type of graph to use.
184
185// Loads a FlowModelProto proto into the MinCostFlow class and solves it.
186void SolveMinCostFlow(const FlowModelProto& flow_model, double* loading_time,
187 double* solving_time) {
188 WallTimer timer;
189 timer.Start();
190
191 // Compute the number of nodes.
192 int64_t num_nodes = 0;
193 for (int i = 0; i < flow_model.arcs_size(); ++i) {
194 num_nodes = std::max(num_nodes, flow_model.arcs(i).tail() + 1);
195 num_nodes = std::max(num_nodes, flow_model.arcs(i).head() + 1);
196 }
197
198 // Build the graph.
199 Graph graph(num_nodes, flow_model.arcs_size());
200 for (int i = 0; i < flow_model.arcs_size(); ++i) {
201 graph.AddArc(flow_model.arcs(i).tail(), flow_model.arcs(i).head());
202 }
203 std::vector<Graph::ArcIndex> permutation;
204 graph.Build(&permutation);
205 absl::PrintF("%d,", graph.num_nodes());
206 absl::PrintF("%d,", graph.num_arcs());
207
208 GenericMinCostFlow<Graph> min_cost_flow(&graph);
209 for (int i = 0; i < flow_model.arcs_size(); ++i) {
210 const Graph::ArcIndex image = i < permutation.size() ? permutation[i] : i;
211 min_cost_flow.SetArcUnitCost(image, flow_model.arcs(i).unit_cost());
212 min_cost_flow.SetArcCapacity(image, flow_model.arcs(i).capacity());
213 }
214 for (int i = 0; i < flow_model.nodes_size(); ++i) {
215 min_cost_flow.SetNodeSupply(flow_model.nodes(i).id(),
216 flow_model.nodes(i).supply());
217 }
218
219 *loading_time = timer.Get();
220 absl::PrintF("%f,", *loading_time);
221 fflush(stdout);
222
223 timer.Start();
224 CHECK(min_cost_flow.Solve());
225 CHECK_EQ(GenericMinCostFlow<Graph>::OPTIMAL, min_cost_flow.status());
226 *solving_time = timer.Get();
227 absl::PrintF("%f,", *solving_time);
228 absl::PrintF("%d", min_cost_flow.GetOptimalCost());
229 fflush(stdout);
230}
231
232// Loads a FlowModelProto proto into the MaxFlow class and solves it.
233template <typename GraphType>
235 const FlowModelProto& flow_model, double* loading_time,
236 double* solving_time,
237 std::function<void(GraphType* graph)> configure_graph_options = nullptr) {
238 WallTimer timer;
239 timer.Start();
240
241 // Build the graph.
242 GraphType graph(flow_model.nodes_size(), flow_model.arcs_size());
243 for (int i = 0; i < flow_model.arcs_size(); ++i) {
244 graph.AddArc(flow_model.arcs(i).tail(), flow_model.arcs(i).head());
245 }
246 std::vector<typename GraphType::ArcIndex> permutation;
247
248 if (configure_graph_options != nullptr) {
249 configure_graph_options(&graph);
250 }
251 graph.Build(&permutation);
252
253 absl::PrintF("%d,", graph.num_nodes());
254 absl::PrintF("%d,", graph.num_arcs());
255
256 // Find source & sink.
257 typename GraphType::NodeIndex source = -1;
258 typename GraphType::NodeIndex sink = -1;
259 CHECK_EQ(2, flow_model.nodes_size());
260 for (int i = 0; i < flow_model.nodes_size(); ++i) {
261 if (flow_model.nodes(i).supply() > 0) {
262 source = flow_model.nodes(i).id();
263 }
264 if (flow_model.nodes(i).supply() < 0) {
265 sink = flow_model.nodes(i).id();
266 }
267 }
268 CHECK_NE(source, -1);
269 CHECK_NE(sink, -1);
270
271 // Create the max flow instance and set the arc capacities.
272 GenericMaxFlow<GraphType> max_flow(&graph, source, sink);
273 for (int i = 0; i < flow_model.arcs_size(); ++i) {
274 const typename GraphType::ArcIndex image =
275 i < permutation.size() ? permutation[i] : i;
276 max_flow.SetArcCapacity(image, flow_model.arcs(i).capacity());
277 }
278
279 *loading_time = timer.Get();
280 absl::PrintF("%f,", *loading_time);
281 fflush(stdout);
282
283 timer.Start();
284 CHECK(max_flow.Solve());
285 CHECK_EQ(GenericMaxFlow<GraphType>::OPTIMAL, max_flow.status());
286 *solving_time = timer.Get();
287 absl::PrintF("%f,", *solving_time);
288 absl::PrintF("%d", max_flow.GetOptimalFlow());
289 fflush(stdout);
290}
291
292} // namespace operations_research
293
298
299int main(int argc, char** argv) {
301 "Runs the OR-tools min-cost flow on a pattern of files given by --input. "
302 "The files must be in Dimacs text format or in binary FlowModelProto "
303 "format.",
304 &argc, &argv, true);
305 absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
306 if (absl::GetFlag(FLAGS_input).empty()) {
307 LOG(FATAL) << "Please specify input pattern via --input=...";
308 }
309 std::vector<std::string> file_list;
310 file::Match(absl::GetFlag(FLAGS_input), &file_list, file::Defaults())
311 .IgnoreError();
312
313 TimeDistribution parsing_time_distribution("Parsing time summary");
314 TimeDistribution loading_time_distribution("Loading time summary");
315 TimeDistribution solving_time_distribution("Solving time summary");
316
317 absl::PrintF(
318 "file_name, parsing_time, num_nodes, num_arcs,loading_time, "
319 "solving_time, optimal_cost\n");
320 for (int i = 0; i < file_list.size(); ++i) {
321 const std::string file_name = file_list[i];
322 absl::PrintF("%s,", file::Basename(file_name));
323 fflush(stdout);
324
325 // Parse the input as a proto.
326 double parsing_time = 0;
328 if (absl::EndsWith(file_name, ".bin") ||
329 absl::EndsWith(file_name, ".bin.gz")) {
330 ScopedWallTime timer(&parsing_time);
331 if (absl::EndsWith(file_name, "gz")) {
332 std::string raw_data;
333 CHECK_OK(file::GetContents(file_name, &raw_data, file::Defaults()));
334 CHECK_OK(StringToProto(raw_data, &proto));
335 } else {
336 CHECK_OK(file::GetBinaryProto(file_name, &proto, file::Defaults()));
337 }
338 } else {
339 ScopedWallTime timer(&parsing_time);
340 if (!ConvertDimacsToFlowModel(file_name, &proto)) continue;
341 }
342 absl::PrintF("%f,", parsing_time);
343 fflush(stdout);
344
345 if (!absl::GetFlag(FLAGS_output_proto).empty()) {
346 LOG(INFO) << "Dumping binary proto to '"
347 << absl::GetFlag(FLAGS_output_proto) << "'.";
348 CHECK_OK(file::SetBinaryProto(absl::GetFlag(FLAGS_output_proto), proto,
349 file::Defaults()));
350 }
351
352 // TODO(user): improve code to convert many files.
353 if (!absl::GetFlag(FLAGS_output_dimacs).empty()) {
354 LOG(INFO) << "Converting first input file to dimacs format.";
355 double time = 0;
356 {
357 ScopedWallTime timer(&time);
358 std::string dimacs;
359 ConvertFlowModelToDimacs(proto, &dimacs);
360 CHECK_OK(file::SetContents(absl::GetFlag(FLAGS_output_dimacs), dimacs,
361 file::Defaults()));
362 }
363 LOG(INFO) << "Done: " << time << "s.";
364 return EXIT_SUCCESS;
365 }
366
367 double loading_time = 0;
368 double solving_time = 0;
369 switch (proto.problem_type()) {
371 SolveMinCostFlow(proto, &loading_time, &solving_time);
372 break;
374 if (absl::GetFlag(FLAGS_use_flow_graph)) {
376 proto, &loading_time, &solving_time,
377 [](util::FlowGraph<>* graph) {
378 graph->SetDetectReverse(
379 absl::GetFlag(FLAGS_detect_reverse_arcs));
380 graph->SetSortByHead(absl::GetFlag(FLAGS_sort_heads));
381 });
382 } else {
383 SolveMaxFlow<util::ReverseArcStaticGraph<>>(proto, &loading_time,
384 &solving_time);
385 }
386 break;
387 default:
388 LOG(ERROR) << "Problem type not supported: " << proto.problem_type();
389 }
390 absl::PrintF("\n");
391
392 parsing_time_distribution.AddTimeInSec(parsing_time);
393 loading_time_distribution.AddTimeInSec(loading_time);
394 solving_time_distribution.AddTimeInSec(solving_time);
395 }
396 absl::PrintF("%s", parsing_time_distribution.StatString());
397 absl::PrintF("%s", loading_time_distribution.StatString());
398 absl::PrintF("%s", solving_time_distribution.StatString());
399 return EXIT_SUCCESS;
400}
static constexpr ProblemType MIN_COST_FLOW
static constexpr ProblemType MAX_FLOW
double Get() const
Definition timer.h:44
void Start()
When Start() is called multiple times, only the most recent is used.
Definition timer.h:30
void set_unit_cost(::int64_t value)
::operations_research::FlowNodeProto *PROTOBUF_NONNULL add_nodes()
::operations_research::FlowModelProto_ProblemType problem_type() const
const ::operations_research::FlowNodeProto & nodes(int index) const
void set_problem_type(::operations_research::FlowModelProto_ProblemType value)
int arcs_size() const
repeated .operations_research.FlowArcProto arcs = 2;
ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL
::operations_research::FlowArcProto *PROTOBUF_NONNULL add_arcs()
const ::operations_research::FlowArcProto & arcs(int index) const
static constexpr ProblemType MIN_COST_FLOW
static constexpr ProblemType MAX_FLOW
FlowModelProto_ProblemType ProblemType
nested types -------------------------------------------------—
int nodes_size() const
repeated .operations_research.FlowNodeProto nodes = 1;
bool Solve()
Returns true if a maximum flow was solved.
FlowSumType GetOptimalFlow() const
Returns the total flow found by the algorithm.
void SetArcCapacity(ArcIndex arc, ArcFlowType new_capacity)
void SetArcUnitCost(ArcIndex arc, ArcScaledCostType unit_cost)
Sets the unit cost for the given arc.
bool Solve()
Solves the problem, returning true if a min-cost flow could be found.
void SetArcCapacity(ArcIndex arc, ArcFlowType new_capacity)
Sets the capacity for the given arc.
void SetNodeSupply(NodeIndex node, FlowQuantity supply)
std::string StatString() const
Definition stats.cc:54
void AddTimeInSec(double seconds)
Adds a time in seconds to this distribution.
Definition stats.cc:200
NodeIndexType num_nodes() const
Definition graph.h:230
ArcIndexType num_arcs() const
Returns the number of valid arcs in the graph.
Definition graph.h:234
ArcIndexType AddArc(NodeIndexType tail, NodeIndexType head)
Definition graph.h:1741
void InitGoogle(absl::string_view usage, int *argc, char ***argv, bool deprecated)
Definition init_google.h:49
Definition file.cc:326
absl::StatusOr< std::string > GetContents(absl::string_view path, Options options)
-— Content API -—
Definition file.cc:348
absl::Status SetBinaryProto(absl::string_view file_name, const google::protobuf::Message &proto, Options options)
Definition file.cc:475
absl::Status Match(std::string_view pattern, std::vector< std::string > *result, const file::Options &options)
Definition filesystem.cc:33
absl::Status GetBinaryProto(const absl::string_view file_name, google::protobuf::Message *proto, Options options)
Definition file.cc:462
absl::string_view Basename(absl::string_view path)
Definition path.cc:109
Options Defaults()
Definition file.h:85
absl::Status SetContents(absl::string_view file_name, absl::string_view contents, Options options)
Definition file.cc:388
In SWIG mode, we don't want anything besides these top-level includes.
void ConvertFlowModelToDimacs(const FlowModelProto &flow_model, std::string *dimacs)
bool ConvertDimacsToFlowModel(absl::string_view file, FlowModelProto *flow_model)
void SolveMinCostFlow(const FlowModelProto &flow_model, double *loading_time, double *solving_time)
Loads a FlowModelProto proto into the MinCostFlow class and solves it.
util::ReverseArcStaticGraph Graph
Type of graph to use.
void SolveMaxFlow(const FlowModelProto &flow_model, double *loading_time, double *solving_time, std::function< void(GraphType *graph)> configure_graph_options=nullptr)
Loads a FlowModelProto proto into the MaxFlow class and solves it.
static int input(yyscan_t yyscanner)
int main(int argc, char **argv)
void SolveMinCostFlow(const FlowModelProto &flow_model, double *loading_time, double *solving_time)
Loads a FlowModelProto proto into the MinCostFlow class and solves it.
ABSL_FLAG(std::string, input, "", "Input file of the problem.")
void SolveMaxFlow(const FlowModelProto &flow_model, double *loading_time, double *solving_time, std::function< void(GraphType *graph)> configure_graph_options=nullptr)
Loads a FlowModelProto proto into the MaxFlow class and solves it.