OR-Tools  9.1
io.h
Go to the documentation of this file.
1// Copyright 2010-2021 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// A collections of i/o utilities for the Graph classes in ./graph.h.
15
16#ifndef UTIL_GRAPH_IO_H_
17#define UTIL_GRAPH_IO_H_
18
19#include <algorithm>
20#include <cstdint>
21#include <memory>
22#include <numeric>
23#include <string>
24#include <vector>
25
26#include "absl/status/status.h"
27#include "absl/status/statusor.h"
28#include "absl/strings/numbers.h"
29#include "absl/strings/str_format.h"
30#include "absl/strings/str_join.h"
31#include "absl/strings/str_split.h"
33#include "ortools/graph/graph.h"
34
35namespace util {
36
37// Returns a string representation of a graph.
39 // One arc per line, eg. "3->1".
41
42 // One space-separated adjacency list per line, eg. "3: 5 1 3 1".
43 // Nodes with no outgoing arc get an empty list.
45
46 // Ditto, but the adjacency lists are sorted.
48};
49template <class Graph>
50std::string GraphToString(const Graph& graph, GraphToStringFormat format);
51
52// Read a graph file in the simple ".g" format: the file should be a text file
53// containing only space-separated integers, whose first line is:
54// <num nodes> <num edges> [<num_colors> <index of first node with color #1>
55// <index of first node with color #2> ...]
56// and whose subsequent lines represent edges if "directed" is false, or arcs if
57// "directed" is true:
58// <node1> <node2>.
59//
60// This returns a newly created graph upon success, which the user needs to take
61// ownership of, or a failure status. See absl/status/statusor.h.
62//
63// If "num_nodes_with_color_or_null" is not nullptr, it will be filled with the
64// color information: num_nodes_with_color_or_null[i] will be the number of
65// nodes with color #i. Furthermore, nodes are sorted by color.
66//
67// Examples:
68// // Simply crash if the graph isn't successfully read from the file.
69// typedef StaticGraph<> MyGraph; // This is just an example.
70// std::unique_ptr<MyGraph> my_graph(
71// ReadGraphFile<MyGraph>("graph.g", /*directed=*/ false).ValueOrDie());
72//
73// // More complicated error handling.
74// absl::StatusOr<MyGraph*> error_or_graph =
75// ReadGraphFile<MyGraph>("graph.g", /*directed=*/ false);
76// if (!error_or_graph.ok()) {
77// LOG(ERROR) << "Error: " << error_or_graph.status().error_message();
78// } else {
79// std::unique_ptr<MyGraph> my_graph(error_or_graph.ValueOrDie());
80// ...
81// }
82template <class Graph>
83absl::StatusOr<Graph*> ReadGraphFile(
84 const std::string& filename, bool directed,
85 std::vector<int>* num_nodes_with_color_or_null);
86
87// Writes a graph to the ".g" file format described above. If "directed" is
88// true, all arcs are written to the file. If it is false, the graph is expected
89// to be undirected (i.e. the number of arcs a->b is equal to the number of arcs
90// b->a for all nodes a,b); and only the arcs a->b where a<=b are written. Note
91// however that in this case, the symmetry of the graph is not fully checked
92// (only the parity of the number of non-self arcs is).
93//
94// "num_nodes_with_color" is optional. If it is not empty, then the color
95// information will be written to the header of the .g file. See ReadGraphFile.
96//
97// This method is the reverse of ReadGraphFile (with the same value for
98// "directed").
99template <class Graph>
100absl::Status WriteGraphToFile(const Graph& graph, const std::string& filename,
101 bool directed,
102 const std::vector<int>& num_nodes_with_color);
103
104// Implementations of the templated methods.
105
106template <class Graph>
107std::string GraphToString(const Graph& graph, GraphToStringFormat format) {
108 std::string out;
109 std::vector<typename Graph::NodeIndex> adj;
110 for (const typename Graph::NodeIndex node : graph.AllNodes()) {
111 if (format == PRINT_GRAPH_ARCS) {
112 for (const typename Graph::ArcIndex arc : graph.OutgoingArcs(node)) {
113 if (!out.empty()) out += '\n';
114 absl::StrAppend(&out, node, "->", graph.Head(arc));
115 }
116 } else { // PRINT_GRAPH_ADJACENCY_LISTS[_SORTED]
117 adj.clear();
118 for (const typename Graph::ArcIndex arc : graph.OutgoingArcs(node)) {
119 adj.push_back(graph.Head(arc));
120 }
122 std::sort(adj.begin(), adj.end());
123 }
124 if (node != 0) out += '\n';
125 absl::StrAppend(&out, node, ": ", absl::StrJoin(adj, " "));
126 }
127 }
128 return out;
129}
130
131template <class Graph>
132absl::StatusOr<Graph*> ReadGraphFile(
133 const std::string& filename, bool directed,
134 std::vector<int>* num_nodes_with_color_or_null) {
135 std::unique_ptr<Graph> graph;
136 int64_t num_nodes = -1;
137 int64_t num_expected_lines = -1;
138 int64_t num_lines_read = 0;
139 for (const std::string& line : FileLines(filename)) {
140 ++num_lines_read;
141 if (num_lines_read == 1) {
142 std::vector<int64_t> header_ints;
143 // if (!SplitStringAndParse(line, " ", &strings::safe_strto64,
144 // &header_ints) ||
145 // header_ints.size() < 2 || header_ints[0] < 0 || header_ints[1] < 0)
146 // {
147 // return absl::Status(
148 // absl::StatusCode::kInvalidArgument,
149 // absl::StrCat("First line of '", filename,
150 // "' should be at least two nonnegative integers."));
151 // }
152 num_nodes = header_ints[0];
153 num_expected_lines = header_ints[1];
154 if (num_nodes_with_color_or_null != nullptr) {
155 num_nodes_with_color_or_null->clear();
156 if (header_ints.size() == 2) {
157 // No coloring: all the nodes have the same color.
158 num_nodes_with_color_or_null->push_back(num_nodes);
159 } else {
160 const int num_colors = header_ints[2];
161 if (header_ints.size() != num_colors + 2) {
162 return absl::Status(
163 absl::StatusCode::kInvalidArgument,
164 absl::StrFormat(
165 "There should be num_colors-1 color cardinalities in the"
166 " header of '%s' (where num_colors=%d): the last color"
167 " cardinality should be skipped",
168 filename, num_colors));
169 }
170 num_nodes_with_color_or_null->reserve(num_colors);
171 int num_nodes_left = num_nodes;
172 for (int i = 3; i < header_ints.size(); ++i) {
173 num_nodes_with_color_or_null->push_back(header_ints[i]);
174 num_nodes_left -= header_ints[i];
175 if (header_ints[i] <= 0 || num_nodes_left <= 0) {
176 return absl::Status(
177 absl::StatusCode::kInvalidArgument,
178 absl::StrFormat("The color cardinalities in the header of"
179 " '%s' should always be >0 and add up to less"
180 " than the total number of nodes",
181 filename));
182 }
183 }
184 num_nodes_with_color_or_null->push_back(num_nodes_left);
185 }
186 }
187 const int64_t num_arcs = (directed ? 1 : 2) * num_expected_lines;
188 graph.reset(new Graph(num_nodes, num_arcs));
189 continue;
190 }
191 size_t space_pos = line.find(' ');
192 int64_t node1 = -1;
193 int64_t node2 = -1;
194 bool parse_success = false;
195 if (space_pos != std::string::npos) {
196 if (absl::SimpleAtoi(absl::string_view(line.c_str(), space_pos),
197 &node1) &&
198 absl::SimpleAtoi(absl::string_view(line.c_str() + space_pos + 1),
199 &node2)) {
200 parse_success =
201 node1 >= 0 && node1 < num_nodes && node2 >= 0 && node2 < num_nodes;
202 }
203 }
204 if (!parse_success) {
205 return absl::Status(
206 absl::StatusCode::kInvalidArgument,
207 absl::StrFormat(
208 "In '%s', line %d: Expected two integers in the range [0, %d).",
209 filename, num_lines_read, num_nodes));
210 }
211 // We don't add superfluous arcs to the graph, but we still keep reading
212 // the file, to get better error messages: we want to know the actual
213 // number of lines, and also want to check the validity of the superfluous
214 // arcs (i.e. that their src/dst nodes are ok).
215 if (num_lines_read > num_expected_lines + 1) continue;
216 graph->AddArc(node1, node2);
217 if (!directed && node1 != node2) graph->AddArc(node2, node1);
218 }
219 if (num_lines_read == 0) {
220 return absl::Status(absl::StatusCode::kInvalidArgument,
221 "Unknown or empty file");
222 }
223 if (num_lines_read != num_expected_lines + 1) {
224 return absl::Status(
225 absl::StatusCode::kInvalidArgument,
226 absl::StrFormat("The number of arcs/edges in '%s' (%d) does not match"
227 " the value announced in the header (%d)",
228 filename, num_lines_read - 1, num_expected_lines));
229 }
230 graph->Build();
231 return graph.release();
232}
233
234template <class Graph>
235absl::Status WriteGraphToFile(const Graph& graph, const std::string& filename,
236 bool directed,
237 const std::vector<int>& num_nodes_with_color) {
238 FILE* f = fopen(filename.c_str(), "w");
239 if (f == nullptr) {
240 return absl::Status(absl::StatusCode::kInvalidArgument,
241 "Could not open file: '" + filename + "'");
242 }
243 // In undirected mode, we must count the self-arcs separately. All other arcs
244 // should be duplicated.
245 int num_self_arcs = 0;
246 if (!directed) {
247 for (const typename Graph::NodeIndex node : graph.AllNodes()) {
248 for (const typename Graph::ArcIndex arc : graph.OutgoingArcs(node)) {
249 if (graph.Head(arc) == node) ++num_self_arcs;
250 }
251 }
252 if ((graph.num_arcs() - num_self_arcs) % 2 != 0) {
253 fclose(f);
254 return absl::Status(absl::StatusCode::kInvalidArgument,
255 "WriteGraphToFile() called with directed=false"
256 " and with a graph with an odd number of (non-self)"
257 " arcs!");
258 }
259 }
260 absl::FPrintF(
261 f, "%d %d", static_cast<int64_t>(graph.num_nodes()),
262 static_cast<int64_t>(directed ? graph.num_arcs()
263 : (graph.num_arcs() + num_self_arcs) / 2));
264 if (!num_nodes_with_color.empty()) {
265 if (std::accumulate(num_nodes_with_color.begin(),
266 num_nodes_with_color.end(), 0) != graph.num_nodes() ||
267 *std::min_element(num_nodes_with_color.begin(),
268 num_nodes_with_color.end()) <= 0) {
269 return absl::Status(absl::StatusCode::kInvalidArgument,
270 "WriteGraphToFile() called with invalid coloring.");
271 }
272 absl::FPrintF(f, " %d", num_nodes_with_color.size());
273 for (int i = 0; i < num_nodes_with_color.size() - 1; ++i) {
274 absl::FPrintF(f, " %d", static_cast<int64_t>(num_nodes_with_color[i]));
275 }
276 }
277 absl::FPrintF(f, "\n");
278
279 for (const typename Graph::NodeIndex node : graph.AllNodes()) {
280 for (const typename Graph::ArcIndex arc : graph.OutgoingArcs(node)) {
281 const typename Graph::NodeIndex head = graph.Head(arc);
282 if (directed || head >= node) {
283 absl::FPrintF(f, "%d %d\n", static_cast<int64_t>(node),
284 static_cast<uint64_t>(head));
285 }
286 }
287 }
288 if (fclose(f) != 0) {
289 return absl::Status(absl::StatusCode::kInternal,
290 "Could not close file '" + filename + "'");
291 }
292 return ::absl::OkStatus();
293}
294
295} // namespace util
296
297#endif // UTIL_GRAPH_IO_H_
ArcIndexType num_arcs() const
Definition: graph.h:206
NodeIndexType num_nodes() const
Definition: graph.h:203
IntegerRange< NodeIndex > AllNodes() const
Definition: graph.h:936
NodeIndexType Head(ArcIndexType arc) const
Definition: graph.h:1118
BeginEndWrapper< OutgoingArcIterator > OutgoingArcs(NodeIndexType node) const
GraphToStringFormat
Definition: io.h:38
@ PRINT_GRAPH_ARCS
Definition: io.h:40
@ PRINT_GRAPH_ADJACENCY_LISTS
Definition: io.h:44
@ PRINT_GRAPH_ADJACENCY_LISTS_SORTED
Definition: io.h:47
ListGraph Graph
Definition: graph.h:2361
std::string GraphToString(const Graph &graph, GraphToStringFormat format)
Definition: io.h:107
absl::Status WriteGraphToFile(const Graph &graph, const std::string &filename, bool directed, const std::vector< int > &num_nodes_with_color)
Definition: io.h:235
absl::StatusOr< Graph * > ReadGraphFile(const std::string &filename, bool directed, std::vector< int > *num_nodes_with_color_or_null)
Definition: io.h:132
int64_t head