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