Google OR-Tools v9.11
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-2024 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 <cstdlib>
25#include <sstream>
26#include <string>
27#include <vector>
28
29#include "absl/flags/flag.h"
30#include "absl/status/status.h"
31#include "absl/strings/match.h"
32#include "absl/strings/str_format.h"
33#include "absl/strings/string_view.h"
35#include "ortools/base/file.h"
40#include "ortools/base/timer.h"
41#include "ortools/graph/flow_problem.pb.h"
42#include "ortools/graph/graph.h"
46#include "ortools/util/stats.h"
47
48ABSL_FLAG(std::string, input, "", "Input file of the problem.");
49ABSL_FLAG(std::string, output_dimacs, "", "Output problem as a dimacs file.");
50
51namespace operations_research {
52
53// See http://lpsolve.sourceforge.net/5.5/DIMACS_mcf.htm for the dimacs file
54// format of a min cost flow problem.
55//
56// TODO(user): This currently only works for min cost flow problem.
57void ConvertFlowModelToDimacs(const FlowModelProto& flow_model,
58 std::string* dimacs) {
59 CHECK_EQ(FlowModelProto::MIN_COST_FLOW, flow_model.problem_type());
60 dimacs->clear();
61 dimacs->append("c Min-Cost flow problem generated from a FlowModelProto.\n");
62
63 // We need to compute the num_nodes from the nodes appearing in the arcs.
64 int64_t num_nodes = 0;
65 for (int64_t i = 0; i < flow_model.arcs_size(); ++i) {
66 num_nodes = std::max(num_nodes, flow_model.arcs(i).tail() + 1);
67 num_nodes = std::max(num_nodes, flow_model.arcs(i).head() + 1);
68 }
69
70 // Problem size and type.
71 const int64_t num_arcs = flow_model.arcs_size();
72 dimacs->append("c\nc Problem line (problem_type, num nodes, num arcs).\n");
73 dimacs->append(absl::StrFormat("p min %d %d\n", num_nodes, num_arcs));
74
75 // Nodes.
76 dimacs->append("c\nc Node descriptor lines (id, supply/demand).\n");
77 for (int64_t i = 0; i < flow_model.nodes_size(); ++i) {
78 const int64_t id = flow_model.nodes(i).id() + 1;
79 const int64_t supply = flow_model.nodes(i).supply();
80 if (supply != 0) {
81 dimacs->append(absl::StrFormat("n %d %d\n", id, supply));
82 }
83 }
84
85 // Arcs.
86 dimacs->append(
87 "c\nc Arc descriptor Lines (tail, head, minflow, maxflow, cost).\n");
88 for (int64_t i = 0; i < flow_model.arcs_size(); ++i) {
89 const int64_t tail = flow_model.arcs(i).tail() + 1;
90 const int64_t head = flow_model.arcs(i).head() + 1;
91 const int64_t unit_cost = flow_model.arcs(i).unit_cost();
92 const int64_t capacity = flow_model.arcs(i).capacity();
93 dimacs->append(absl::StrFormat("a %d %d %d %d %d\n", tail, head, int64_t{0},
94 capacity, unit_cost));
95 }
96 dimacs->append("c\n");
97}
98
99// Note(user): Going from Dimacs to flow adds an extra copy, but for now we
100// don't really care of the Dimacs file reading performance.
101// Returns true if the file was converted correctly.
102bool ConvertDimacsToFlowModel(absl::string_view file,
103 FlowModelProto* flow_model) {
104 flow_model->Clear();
105 FlowModelProto::ProblemType problem_type;
106 for (const std::string& line : FileLines(file)) {
107 if (line.empty()) continue;
108 if (line[0] == 'p') {
109 if (absl::StartsWith(line, "p min")) {
110 problem_type = FlowModelProto::MIN_COST_FLOW;
111 } else if (absl::StartsWith(line, "p max")) {
112 problem_type = FlowModelProto::MAX_FLOW;
113 } else {
114 LOG(ERROR) << "Unknown dimacs problem format.";
115 return false;
116 }
117 flow_model->set_problem_type(problem_type);
118 }
119 if (line[0] == 'n') {
120 int64_t id;
121 int64_t supply;
122 std::stringstream ss(line.substr(1));
123 switch (problem_type) {
124 case FlowModelProto::MIN_COST_FLOW:
125 ss >> id >> supply;
126 break;
127 case FlowModelProto::MAX_FLOW: {
128 std::string type;
129 ss >> id >> type;
130 supply = (type == "s" ? 1 : -1);
131 break;
132 }
133 default:
134 LOG(ERROR) << "Node line before the problem type definition.";
135 return false;
136 }
137 FlowNodeProto* node = flow_model->add_nodes();
138 node->set_id(id - 1);
139 node->set_supply(supply);
140 }
141 if (line[0] == 'a') {
142 int64_t tail;
143 int64_t head;
144 int64_t min_capacity = 0;
145 int64_t max_capacity = 0;
146 int64_t unit_cost = 0;
147 std::stringstream ss(line.substr(1));
148 switch (problem_type) {
149 case FlowModelProto::MIN_COST_FLOW:
150 ss >> tail >> head >> min_capacity >> max_capacity >> unit_cost;
151 break;
152 case FlowModelProto::MAX_FLOW:
153 ss >> tail >> head >> max_capacity;
154 break;
155 default:
156 LOG(ERROR) << "Arc line before the problem type definition.";
157 return false;
158 }
159 FlowArcProto* arc = flow_model->add_arcs();
160 arc->set_tail(tail - 1);
161 arc->set_head(head - 1);
162 arc->set_capacity(max_capacity);
163 arc->set_unit_cost(unit_cost);
164 if (min_capacity != 0) {
165 LOG(ERROR) << "We do not support minimum capacity.";
166 return false;
167 }
168 }
169 }
170 return true;
171}
172
173// Type of graph to use.
175
176// Loads a FlowModelProto proto into the MinCostFlow class and solves it.
177void SolveMinCostFlow(const FlowModelProto& flow_model, double* loading_time,
178 double* solving_time) {
179 WallTimer timer;
180 timer.Start();
181
182 // Compute the number of nodes.
183 int64_t num_nodes = 0;
184 for (int i = 0; i < flow_model.arcs_size(); ++i) {
185 num_nodes = std::max(num_nodes, flow_model.arcs(i).tail() + 1);
186 num_nodes = std::max(num_nodes, flow_model.arcs(i).head() + 1);
187 }
188
189 // Build the graph.
190 Graph graph(num_nodes, flow_model.arcs_size());
191 for (int i = 0; i < flow_model.arcs_size(); ++i) {
192 graph.AddArc(flow_model.arcs(i).tail(), flow_model.arcs(i).head());
193 }
194 std::vector<Graph::ArcIndex> permutation;
195 graph.Build(&permutation);
196
197 GenericMinCostFlow<Graph> min_cost_flow(&graph);
198 for (int i = 0; i < flow_model.arcs_size(); ++i) {
199 const Graph::ArcIndex image = i < permutation.size() ? permutation[i] : i;
200 min_cost_flow.SetArcUnitCost(image, flow_model.arcs(i).unit_cost());
201 min_cost_flow.SetArcCapacity(image, flow_model.arcs(i).capacity());
202 }
203 for (int i = 0; i < flow_model.nodes_size(); ++i) {
204 min_cost_flow.SetNodeSupply(flow_model.nodes(i).id(),
205 flow_model.nodes(i).supply());
206 }
207
208 *loading_time = timer.Get();
209 absl::PrintF("%f,", *loading_time);
210 fflush(stdout);
211
212 timer.Start();
213 CHECK(min_cost_flow.Solve());
214 CHECK_EQ(GenericMinCostFlow<Graph>::OPTIMAL, min_cost_flow.status());
215 *solving_time = timer.Get();
216 absl::PrintF("%f,", *solving_time);
217 absl::PrintF("%d", min_cost_flow.GetOptimalCost());
218 fflush(stdout);
219}
220
221// Loads a FlowModelProto proto into the MaxFlow class and solves it.
222void SolveMaxFlow(const FlowModelProto& flow_model, double* loading_time,
223 double* solving_time) {
224 WallTimer timer;
225 timer.Start();
226
227 // Compute the number of nodes.
228 int64_t num_nodes = 0;
229 for (int i = 0; i < flow_model.arcs_size(); ++i) {
230 num_nodes = std::max(num_nodes, flow_model.arcs(i).tail() + 1);
231 num_nodes = std::max(num_nodes, flow_model.arcs(i).head() + 1);
232 }
233
234 // Build the graph.
235 Graph graph(num_nodes, flow_model.arcs_size());
236 for (int i = 0; i < flow_model.arcs_size(); ++i) {
237 graph.AddArc(flow_model.arcs(i).tail(), flow_model.arcs(i).head());
238 }
239 std::vector<Graph::ArcIndex> permutation;
240 graph.Build(&permutation);
241
242 // Find source & sink.
243 Graph::NodeIndex source = -1;
244 Graph::NodeIndex sink = -1;
245 CHECK_EQ(2, flow_model.nodes_size());
246 for (int i = 0; i < flow_model.nodes_size(); ++i) {
247 if (flow_model.nodes(i).supply() > 0) {
248 source = flow_model.nodes(i).id();
249 }
250 if (flow_model.nodes(i).supply() < 0) {
251 sink = flow_model.nodes(i).id();
252 }
253 }
254 CHECK_NE(source, -1);
255 CHECK_NE(sink, -1);
256
257 // Create the max flow instance and set the arc capacities.
258 GenericMaxFlow<Graph> max_flow(&graph, source, sink);
259 for (int i = 0; i < flow_model.arcs_size(); ++i) {
260 const Graph::ArcIndex image = i < permutation.size() ? permutation[i] : i;
261 max_flow.SetArcCapacity(image, flow_model.arcs(i).capacity());
262 }
263
264 *loading_time = timer.Get();
265 absl::PrintF("%f,", *loading_time);
266 fflush(stdout);
267
268 timer.Start();
269 CHECK(max_flow.Solve());
270 CHECK_EQ(GenericMaxFlow<Graph>::OPTIMAL, max_flow.status());
271 *solving_time = timer.Get();
272 absl::PrintF("%f,", *solving_time);
273 absl::PrintF("%d", max_flow.GetOptimalFlow());
274 fflush(stdout);
275}
276
277} // namespace operations_research
278
279using operations_research::FlowModelProto;
283
284int main(int argc, char** argv) {
286 "Runs the OR-tools min-cost flow on a pattern of files given by --input. "
287 "The files must be in Dimacs text format or in binary FlowModelProto "
288 "format.",
289 &argc, &argv, true);
290 absl::SetFlag(&FLAGS_stderrthreshold, 0);
291 if (absl::GetFlag(FLAGS_input).empty()) {
292 LOG(FATAL) << "Please specify input pattern via --input=...";
293 }
294 std::vector<std::string> file_list;
295 file::Match(absl::GetFlag(FLAGS_input), &file_list, file::Defaults())
296 .IgnoreError();
297
298 TimeDistribution parsing_time_distribution("Parsing time summary");
299 TimeDistribution loading_time_distribution("Loading time summary");
300 TimeDistribution solving_time_distribution("Solving time summary");
301
302 absl::PrintF(
303 "file_name, parsing_time, loading_time, solving_time, optimal_cost\n");
304 for (int i = 0; i < file_list.size(); ++i) {
305 const std::string file_name = file_list[i];
306 absl::PrintF("%s,", file_name);
307 fflush(stdout);
308
309 // Parse the input as a proto.
310 double parsing_time = 0;
311 operations_research::FlowModelProto proto;
312 if (absl::EndsWith(file_name, ".bin")) {
313 ScopedWallTime timer(&parsing_time);
314 std::string raw_data;
315 CHECK_OK(file::GetContents(file_name, &raw_data, file::Defaults()));
316 proto.ParseFromString(raw_data);
317 } else {
318 ScopedWallTime timer(&parsing_time);
319 if (!ConvertDimacsToFlowModel(file_name, &proto)) continue;
320 }
321 absl::PrintF("%f,", parsing_time);
322 fflush(stdout);
323
324 // TODO(user): improve code to convert many files.
325 if (!absl::GetFlag(FLAGS_output_dimacs).empty()) {
326 LOG(INFO) << "Converting first input file to dimacs format.";
327 double time = 0;
328 {
329 ScopedWallTime timer(&time);
330 std::string dimacs;
331 ConvertFlowModelToDimacs(proto, &dimacs);
332 CHECK_OK(file::SetContents(absl::GetFlag(FLAGS_output_dimacs), dimacs,
333 file::Defaults()));
334 }
335 LOG(INFO) << "Done: " << time << "s.";
336 return EXIT_SUCCESS;
337 }
338
339 double loading_time = 0;
340 double solving_time = 0;
341 switch (proto.problem_type()) {
342 case FlowModelProto::MIN_COST_FLOW:
343 SolveMinCostFlow(proto, &loading_time, &solving_time);
344 break;
345 case FlowModelProto::MAX_FLOW:
346 SolveMaxFlow(proto, &loading_time, &solving_time);
347 break;
348 default:
349 LOG(ERROR) << "Problem type not supported: " << proto.problem_type();
350 }
351 absl::PrintF("\n");
352
353 parsing_time_distribution.AddTimeInSec(parsing_time);
354 loading_time_distribution.AddTimeInSec(loading_time);
355 solving_time_distribution.AddTimeInSec(solving_time);
356 }
357 absl::PrintF("%s", parsing_time_distribution.StatString());
358 absl::PrintF("%s", loading_time_distribution.StatString());
359 absl::PrintF("%s", solving_time_distribution.StatString());
360 return EXIT_SUCCESS;
361}
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
void SetArcCapacity(ArcIndex arc, FlowQuantity new_capacity)
Sets the capacity for arc to new_capacity.
Definition max_flow.cc:194
bool Solve()
Returns true if a maximum flow was solved.
Definition max_flow.cc:353
FlowQuantity GetOptimalFlow() const
Returns the total flow found by the algorithm.
Definition max_flow.h:377
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
CpModelProto proto
The output proto.
GraphType graph
void InitGoogle(const char *usage, int *argc, char ***argv, bool deprecated)
Definition init_google.h:34
MPSolver::OptimizationProblemType problem_type
int arc
Definition file.cc:169
absl::StatusOr< std::string > GetContents(absl::string_view path, Options options)
Definition file.cc:191
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
Options Defaults()
Definition file.h:109
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.
void SolveMaxFlow(const FlowModelProto &flow_model, double *loading_time, double *solving_time)
Loads a FlowModelProto proto into the MaxFlow class and solves it.
util::ReverseArcStaticGraph Graph
Type of graph to use.
int line
static int input(yyscan_t yyscanner)
int64_t time
Definition resource.cc:1708
int head
int tail
int main(int argc, char **argv)
ABSL_FLAG(std::string, input, "", "Input file of the problem.")