2024-01-04 13:43:15 +01:00
|
|
|
// Copyright 2010-2024 Google LLC
|
2012-03-28 18:50:03 +00:00
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
2014-07-09 15:18:27 +00:00
|
|
|
|
2011-11-03 10:27:53 +00:00
|
|
|
//
|
|
|
|
|
// Function for reading and parsing a file in DIMACS format:
|
|
|
|
|
// http://lpsolve.sourceforge.net/5.5/DIMACS_asn.htm
|
|
|
|
|
//
|
2011-06-22 08:40:00 +00:00
|
|
|
|
2011-09-21 15:16:48 +00:00
|
|
|
#ifndef OR_TOOLS_EXAMPLES_PARSE_DIMACS_ASSIGNMENT_H_
|
|
|
|
|
#define OR_TOOLS_EXAMPLES_PARSE_DIMACS_ASSIGNMENT_H_
|
2011-06-22 08:40:00 +00:00
|
|
|
|
2012-06-13 16:14:46 +00:00
|
|
|
#include <algorithm>
|
2023-06-21 17:31:06 +02:00
|
|
|
#include <cinttypes>
|
2012-06-13 16:14:46 +00:00
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstring>
|
2015-08-13 16:00:54 +02:00
|
|
|
#include <memory>
|
2011-06-22 08:40:00 +00:00
|
|
|
#include <string>
|
|
|
|
|
|
2023-10-16 15:48:29 +02:00
|
|
|
#include "absl/flags/declare.h"
|
|
|
|
|
#include "absl/flags/flag.h"
|
|
|
|
|
#include "absl/log/check.h"
|
2023-06-21 17:31:06 +02:00
|
|
|
#include "absl/strings/string_view.h"
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/graph/linear_assignment.h"
|
2022-01-19 10:32:55 +01:00
|
|
|
#include "ortools/util/filelineiter.h"
|
2012-06-13 16:14:46 +00:00
|
|
|
|
2020-10-23 11:50:14 +02:00
|
|
|
ABSL_FLAG(bool, assignment_maximize_cost, false,
|
|
|
|
|
"Negate costs so a max-cost assignment is found.");
|
2011-06-22 08:40:00 +00:00
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
template <typename GraphType>
|
|
|
|
|
class DimacsAssignmentParser {
|
|
|
|
|
public:
|
2024-12-29 19:21:42 +01:00
|
|
|
using NodeIndex = GraphType::NodeIndex;
|
|
|
|
|
using ArcIndex = GraphType::ArcIndex;
|
|
|
|
|
using CostValue = LinearSumAssignment<GraphType>::CostValueT;
|
|
|
|
|
|
2023-06-21 17:31:06 +02:00
|
|
|
explicit DimacsAssignmentParser(absl::string_view filename)
|
2024-12-29 19:21:42 +01:00
|
|
|
: filename_(filename), graph_(nullptr), assignment_(nullptr) {}
|
2012-06-13 16:14:46 +00:00
|
|
|
|
|
|
|
|
// Reads an assignment problem description from the given file in
|
|
|
|
|
// DIMACS format and returns a LinearSumAssignment object representing
|
|
|
|
|
// the problem description. For a description of the format, see
|
|
|
|
|
// http://lpsolve.sourceforge.net/5.5/DIMACS_asn.htm
|
|
|
|
|
//
|
|
|
|
|
// Also returns an error message (empty if no error) and a handle on
|
|
|
|
|
// the underlying graph representation. The error_message pointer must
|
|
|
|
|
// not be NULL because we insist on returning an explanatory message
|
|
|
|
|
// in the case of error. The graph_handle pointer must not be NULL
|
|
|
|
|
// because unless we pass a non-const pointer to the graph
|
|
|
|
|
// representation back to the caller, the caller lacks a good way to
|
|
|
|
|
// free the underlying graph (which isn't owned by the
|
|
|
|
|
// LinearAssignment instance).
|
2020-10-29 14:25:39 +01:00
|
|
|
LinearSumAssignment<GraphType>* Parse(std::string* error_message,
|
|
|
|
|
GraphType** graph);
|
2012-06-13 16:14:46 +00:00
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
private:
|
2020-10-29 14:25:39 +01:00
|
|
|
void ParseProblemLine(const std::string& line);
|
2012-06-13 16:14:46 +00:00
|
|
|
|
2020-10-29 14:25:39 +01:00
|
|
|
void ParseNodeLine(const std::string& line);
|
2012-06-13 16:14:46 +00:00
|
|
|
|
2020-10-29 14:25:39 +01:00
|
|
|
void ParseArcLine(const std::string& line);
|
2012-06-13 16:14:46 +00:00
|
|
|
|
2020-10-29 14:25:39 +01:00
|
|
|
void ParseOneLine(const std::string& line);
|
2012-06-13 16:14:46 +00:00
|
|
|
|
2013-12-16 10:24:42 +00:00
|
|
|
std::string filename_;
|
2012-06-13 16:14:46 +00:00
|
|
|
|
|
|
|
|
struct ErrorTrackingState {
|
|
|
|
|
ErrorTrackingState()
|
2020-10-22 23:36:58 +02:00
|
|
|
: bad(false),
|
|
|
|
|
nodes_described(false),
|
|
|
|
|
reason(nullptr),
|
|
|
|
|
num_left_nodes(0),
|
|
|
|
|
num_arcs(0) {}
|
2012-06-13 16:14:46 +00:00
|
|
|
|
|
|
|
|
bool bad;
|
|
|
|
|
bool nodes_described;
|
2020-10-29 14:25:39 +01:00
|
|
|
const char* reason;
|
2012-06-13 16:14:46 +00:00
|
|
|
NodeIndex num_left_nodes;
|
|
|
|
|
ArcIndex num_arcs;
|
2013-12-16 10:24:42 +00:00
|
|
|
std::unique_ptr<std::string> bad_line;
|
2012-06-13 16:14:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ErrorTrackingState state_;
|
|
|
|
|
|
2024-12-29 19:21:42 +01:00
|
|
|
std::unique_ptr<GraphType> graph_;
|
2012-06-13 16:14:46 +00:00
|
|
|
|
2020-10-29 14:25:39 +01:00
|
|
|
LinearSumAssignment<GraphType>* assignment_;
|
2012-06-13 16:14:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Implementation is below here.
|
|
|
|
|
template <typename GraphType>
|
2020-10-22 23:36:58 +02:00
|
|
|
void DimacsAssignmentParser<GraphType>::ParseProblemLine(
|
2020-10-29 14:25:39 +01:00
|
|
|
const std::string& line) {
|
|
|
|
|
static const char* kIncorrectProblemLine =
|
2012-06-13 16:14:46 +00:00
|
|
|
"Incorrect assignment problem line.";
|
2020-10-29 14:25:39 +01:00
|
|
|
static const char* kAssignmentProblemType = "asn";
|
2012-06-13 16:14:46 +00:00
|
|
|
char problem_type[4];
|
|
|
|
|
NodeIndex num_nodes;
|
|
|
|
|
ArcIndex num_arcs;
|
|
|
|
|
|
2016-06-08 13:21:09 +02:00
|
|
|
if ((sscanf(line.c_str(), "%*c%3s%d%d", problem_type, &num_nodes,
|
|
|
|
|
&num_arcs) != 3) ||
|
2014-01-08 12:01:58 +00:00
|
|
|
(strncmp(kAssignmentProblemType, problem_type,
|
2012-06-13 16:14:46 +00:00
|
|
|
strlen(kAssignmentProblemType)) != 0)) {
|
|
|
|
|
state_.bad = true;
|
|
|
|
|
state_.reason = kIncorrectProblemLine;
|
2013-12-16 10:24:42 +00:00
|
|
|
state_.bad_line.reset(new std::string(line));
|
2012-06-13 16:14:46 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state_.num_arcs = num_arcs;
|
2024-12-29 19:21:42 +01:00
|
|
|
graph_ = std::make_unique<GraphType>(num_nodes, num_arcs);
|
2012-06-13 16:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename GraphType>
|
2020-10-29 14:25:39 +01:00
|
|
|
void DimacsAssignmentParser<GraphType>::ParseNodeLine(const std::string& line) {
|
2012-06-13 16:14:46 +00:00
|
|
|
NodeIndex node_id;
|
2016-06-08 13:21:09 +02:00
|
|
|
if (sscanf(line.c_str(), "%*c%d", &node_id) != 1) {
|
2012-06-13 16:14:46 +00:00
|
|
|
state_.bad = true;
|
2023-06-21 17:31:06 +02:00
|
|
|
state_.reason = "Syntax error in node description.";
|
2013-12-16 10:24:42 +00:00
|
|
|
state_.bad_line.reset(new std::string(line));
|
2012-06-13 16:14:46 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (state_.nodes_described) {
|
|
|
|
|
state_.bad = true;
|
|
|
|
|
state_.reason = "All node description must precede first arc description.";
|
2013-12-16 10:24:42 +00:00
|
|
|
state_.bad_line.reset(new std::string(line));
|
2012-06-13 16:14:46 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2013-12-16 10:24:42 +00:00
|
|
|
state_.num_left_nodes = std::max(state_.num_left_nodes, node_id);
|
2012-06-13 16:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename GraphType>
|
2020-10-29 14:25:39 +01:00
|
|
|
void DimacsAssignmentParser<GraphType>::ParseArcLine(const std::string& line) {
|
2024-12-29 19:21:42 +01:00
|
|
|
if (graph_ == nullptr) {
|
2012-06-13 16:14:46 +00:00
|
|
|
state_.bad = true;
|
|
|
|
|
state_.reason =
|
|
|
|
|
"Problem specification line must precede any arc specification.";
|
2013-12-16 10:24:42 +00:00
|
|
|
state_.bad_line.reset(new std::string(line));
|
2012-06-13 16:14:46 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!state_.nodes_described) {
|
|
|
|
|
state_.nodes_described = true;
|
2017-04-26 17:30:25 +02:00
|
|
|
DCHECK(assignment_ == nullptr);
|
2014-01-08 12:01:58 +00:00
|
|
|
assignment_ = new LinearSumAssignment<GraphType>(state_.num_left_nodes,
|
|
|
|
|
state_.num_arcs);
|
2012-06-13 16:14:46 +00:00
|
|
|
}
|
|
|
|
|
NodeIndex tail;
|
|
|
|
|
NodeIndex head;
|
2023-10-16 15:48:29 +02:00
|
|
|
CostValue cost;
|
2023-06-21 17:31:06 +02:00
|
|
|
if (sscanf(line.c_str(), "%*c%d%d%" SCNd64, &tail, &head, &cost) != 3) {
|
2012-06-13 16:14:46 +00:00
|
|
|
state_.bad = true;
|
|
|
|
|
state_.reason = "Syntax error in arc descriptor.";
|
2013-12-16 10:24:42 +00:00
|
|
|
state_.bad_line.reset(new std::string(line));
|
2012-06-13 16:14:46 +00:00
|
|
|
}
|
2024-12-29 19:21:42 +01:00
|
|
|
ArcIndex arc = graph_->AddArc(tail - 1, head - 1);
|
2023-10-16 15:48:29 +02:00
|
|
|
assignment_->SetArcCost(
|
|
|
|
|
arc, absl::GetFlag(FLAGS_assignment_maximize_cost) ? -cost : cost);
|
2012-06-13 16:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parameters out of style-guide order because this function is used
|
|
|
|
|
// as a callback that varies the input line.
|
|
|
|
|
template <typename GraphType>
|
2020-10-29 14:25:39 +01:00
|
|
|
void DimacsAssignmentParser<GraphType>::ParseOneLine(const std::string& line) {
|
2012-06-13 16:14:46 +00:00
|
|
|
if (state_.bad) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
switch (line[0]) {
|
2020-10-22 23:36:58 +02:00
|
|
|
case 'p': {
|
|
|
|
|
// Problem-specification line
|
|
|
|
|
ParseProblemLine(line);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'c': {
|
|
|
|
|
// Comment; do nothing.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
case 'n': {
|
|
|
|
|
// Node line defining a node on the left side
|
|
|
|
|
ParseNodeLine(line);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 'a': {
|
|
|
|
|
ParseArcLine(line);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case '0':
|
|
|
|
|
case '\n':
|
|
|
|
|
break;
|
|
|
|
|
default: {
|
|
|
|
|
state_.bad = true;
|
|
|
|
|
state_.reason = "Unknown line type in the input.";
|
|
|
|
|
state_.bad_line.reset(new std::string(line));
|
|
|
|
|
break;
|
|
|
|
|
}
|
2012-06-13 16:14:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-03 10:27:53 +00:00
|
|
|
// Reads an assignment problem description from the given file in
|
|
|
|
|
// DIMACS format and returns a LinearSumAssignment object representing
|
|
|
|
|
// the problem description. For a description of the format, see
|
|
|
|
|
// http://lpsolve.sourceforge.net/5.5/DIMACS_asn.htm
|
|
|
|
|
//
|
|
|
|
|
// Also returns an error message (empty if no error) and a handle on
|
|
|
|
|
// the underlying graph representation. The error_message pointer must
|
|
|
|
|
// not be NULL because we insist on returning an explanatory message
|
|
|
|
|
// in the case of error. The graph_handle pointer must not be NULL
|
|
|
|
|
// because unless we pass a non-const pointer to the graph
|
|
|
|
|
// representation back to the caller, the caller lacks a good way to
|
|
|
|
|
// free the underlying graph (which isn't owned by the
|
|
|
|
|
// LinearAssignment instance).
|
2012-06-13 16:14:46 +00:00
|
|
|
template <typename GraphType>
|
2020-10-29 14:25:39 +01:00
|
|
|
LinearSumAssignment<GraphType>* DimacsAssignmentParser<GraphType>::Parse(
|
|
|
|
|
std::string* error_message, GraphType** graph_handle) {
|
2018-04-11 13:47:07 +02:00
|
|
|
CHECK(error_message != nullptr);
|
|
|
|
|
CHECK(graph_handle != nullptr);
|
2016-06-08 13:21:09 +02:00
|
|
|
|
2020-10-29 14:25:39 +01:00
|
|
|
for (const std::string& line : FileLines(filename_)) {
|
2016-06-08 13:21:09 +02:00
|
|
|
if (line.empty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
ParseOneLine(line);
|
|
|
|
|
}
|
2012-06-13 16:14:46 +00:00
|
|
|
|
|
|
|
|
if (state_.bad) {
|
|
|
|
|
*error_message = state_.reason;
|
|
|
|
|
*error_message = *error_message + ": \"" + *state_.bad_line + "\"";
|
2017-04-26 17:30:25 +02:00
|
|
|
return nullptr;
|
2012-06-13 16:14:46 +00:00
|
|
|
}
|
2024-12-29 19:21:42 +01:00
|
|
|
if (graph_ == nullptr) {
|
2012-06-13 16:14:46 +00:00
|
|
|
*error_message = "empty graph description";
|
2017-04-26 17:30:25 +02:00
|
|
|
return nullptr;
|
2012-06-13 16:14:46 +00:00
|
|
|
}
|
2024-12-29 19:21:42 +01:00
|
|
|
graph_->Build();
|
|
|
|
|
assignment_->SetGraph(graph_.get());
|
2012-06-13 16:14:46 +00:00
|
|
|
*error_message = "";
|
|
|
|
|
// Return a handle on the graph to the caller so the caller can free
|
|
|
|
|
// the graph's memory, because the LinearSumAssignment object does
|
|
|
|
|
// not take ownership of the graph and hence will not free it.
|
2024-12-29 19:21:42 +01:00
|
|
|
*graph_handle = graph_.release();
|
2012-06-13 16:14:46 +00:00
|
|
|
return assignment_;
|
|
|
|
|
}
|
2011-06-22 08:40:00 +00:00
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
} // namespace operations_research
|
2011-06-22 08:40:00 +00:00
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
#endif // OR_TOOLS_EXAMPLES_PARSE_DIMACS_ASSIGNMENT_H_
|