GEDLIB  1.0
subgraph.ipp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2018 by David B. Blumenthal *
4  * *
5  * This file is part of GEDLIB. *
6  * *
7  * GEDLIB is free software: you can redistribute it and/or modify it *
8  * under the terms of the GNU Lesser General Public License as published *
9  * by the Free Software Foundation, either version 3 of the License, or *
10  * (at your option) any later version. *
11  * *
12  * GEDLIB is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU Lesser General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU Lesser General Public *
18  * License along with GEDLIB. If not, see <http://www.gnu.org/licenses/>. *
19  * *
20  ***************************************************************************/
21 
27 #ifndef SRC_METHODS_SUBGRAPH_IPP_
28 #define SRC_METHODS_SUBGRAPH_IPP_
29 
30 namespace ged {
31 
32 // === Definitions of destructor and constructor. ===
33 
34 template<class UserNodeLabel, class UserEdgeLabel>
35 Subgraph<UserNodeLabel, UserEdgeLabel>::
36 ~Subgraph() {}
37 
38 template<class UserNodeLabel, class UserEdgeLabel>
39 Subgraph<UserNodeLabel, UserEdgeLabel>::
40 Subgraph(const GEDData<UserNodeLabel, UserEdgeLabel> & ged_data) :
41 LSAPEBasedMethod<UserNodeLabel, UserEdgeLabel>(ged_data),
42 depth_{2},
43 min_depth_{1},
44 max_depth_{5},
45 infile_(""),
46 outfile_(""),
47 exact_options_(""),
48 subgraphs_(),
50  this->compute_lower_bound_ = false;
51 }
52 
53 // === Definitions of member functions inherited from LSAPEBasedMethod.
54 template<class UserNodeLabel, class UserEdgeLabel>
55 void
58  depth_ = 2;
59  min_depth_ = 1;
60  max_depth_ = 5;
61  infile_ = std::string("");
62  outfile_ = std::string("");
64  exact_options_ = std::string("");
65 }
66 
67 template<class UserNodeLabel, class UserEdgeLabel>
68 std::string
71  return "[--depth-range <arg>] [--load <arg>] [--save <arg>] [--subproblem-solver <arg>] [--subproblem-solver-options <arg>]";
72 }
73 
74 template<class UserNodeLabel, class UserEdgeLabel>
75 bool
77 lsape_parse_option_(const std::string & option, const std::string & arg) {
78  bool is_valid_option{false};
79  if (option == "load") {
80  infile_ = arg;
81  is_valid_option = true;
82  }
83  else if (option == "save") {
84  outfile_ = arg;
85  is_valid_option = true;
86  }
87  else if (option == "depth-range") {
88  std::stringstream depth_range(arg);
89  std::string min_depth, max_depth;
90  if (std::getline(depth_range, min_depth, ',') and std::getline(depth_range, max_depth, ',')) {
91  try {
92  min_depth_ = std::stoi(min_depth);
93  }
94  catch (...) {
95  throw Error(std::string("Invalid argument \"") + arg + "\" for option depth-range. Usage: options = \"[--depth-range <smaller convertible to int greater 0>,<larger convertible to int greater 0>] [...]");
96  }
97  try {
98  max_depth_ = std::stoi(max_depth);
99  }
100  catch (...) {
101  throw Error(std::string("Invalid argument \"") + arg + "\" for option depth-range. Usage: options = \"[--depth-range <smaller convertible to int greater 0>,<larger convertible to int greater 0>] [...]");
102  }
103  if ((min_depth_ > max_depth_) or (min_depth_ <= 0) or (max_depth_ <= 0)) {
104  throw Error(std::string("Invalid argument \"") + arg + "\" for option depth-range. Usage: options = \"[--depth-range <smaller convertible to int greater 0>,<larger convertible to int greater 0>] [...]");
105  }
106  }
107  else {
108  throw Error(std::string("Invalid argument \"") + arg + "\" for option depth-range. Usage: options = \"[--depth-range <smaller convertible to int greater 0>,<larger convertible to int greater 0>] [...]");
109  }
110  is_valid_option = true;
111  }
112  else if (option == "subproblem-solver") {
113 #ifdef GUROBI
114  if (arg == "F1") {
115  exact_method_ = Options::GEDMethod::F1;
116  }
117  else if (arg == "F2") {
118  exact_method_ = Options::GEDMethod::F2;
119  }
120  else if (arg == "COMPACT_MIP") {
121  exact_method_ = Options::GEDMethod::COMPACT_MIP;
122  }
123  else if (arg != "ANCHOR_AWARE_GED") {
124  throw ged::Error(std::string("Invalid argument ") + arg + " for option subproblem-solver. Usage: options = \"[--subproblem-solver ANCHOR_AWARE|F1|F2|COMPACT_MIP] [...]\"");
125  }
126 #else
127  if (arg != "ANCHOR_AWARE_GED") {
128  throw ged::Error(std::string("Invalid argument ") + arg + " for option subproblem-solver. Usage: options = \"[--subproblem-solver ANCHOR_AWARE] [...]\"");
129  }
130 #endif
131  is_valid_option = true;
132  }
133  else if (option == "subproblem-solver-options") {
134  exact_options_ = arg;
135  std::size_t bad_option_start{exact_options_.find("--threads")};
136  std::size_t next_option_start;
137  if (bad_option_start != std::string::npos) {
138  next_option_start = exact_options_.find("--", bad_option_start + 1);
139  if (next_option_start != std::string::npos) {
140  exact_options_ = exact_options_.substr(0, bad_option_start) + exact_options_.substr(next_option_start);
141  }
142  else {
143  exact_options_ = exact_options_.substr(0, bad_option_start);
144  }
145  }
146  bad_option_start = exact_options_.find("--relax");
147  if (bad_option_start != std::string::npos) {
148  next_option_start = exact_options_.find("--", bad_option_start + 1);
149  if (next_option_start != std::string::npos) {
150  exact_options_ = exact_options_.substr(0, bad_option_start) + exact_options_.substr(next_option_start);
151  }
152  else {
153  exact_options_ = exact_options_.substr(0, bad_option_start);
154  }
155  }
156  bad_option_start = exact_options_.find("--map-root-to-root");
157  if (bad_option_start != std::string::npos) {
158  next_option_start = exact_options_.find("--", bad_option_start + 1);
159  if (next_option_start != std::string::npos) {
160  exact_options_ = exact_options_.substr(0, bad_option_start) + exact_options_.substr(next_option_start);
161  }
162  else {
163  exact_options_ = exact_options_.substr(0, bad_option_start);
164  }
165  }
166  is_valid_option = true;
167  }
168  return is_valid_option;
169 }
170 
171 template<class UserNodeLabel, class UserEdgeLabel>
172 void
174 lsape_populate_instance_(const GEDGraph & g, const GEDGraph & h, DMatrix & master_problem) {
175 
176 #ifdef _OPENMP
177  omp_set_num_threads(this->num_threads_ - 1);
178 #pragma omp parallel for if(this->num_threads_ > 1)
179 #endif
180  for (std::size_t row_in_master = 0; row_in_master < master_problem.num_rows(); row_in_master++) {
181  GEDMethod<UserNodeLabel, UserEdgeLabel> * exact_method{nullptr};
182 #pragma omp critical
183  {
184  exact_method = exact_method_factory_();
185  }
186  for (std::size_t col_in_master = 0; col_in_master < master_problem.num_cols(); col_in_master++) {
187  if ((row_in_master < g.num_nodes()) and (col_in_master < h.num_nodes())) {
188  master_problem(row_in_master, col_in_master) = compute_substitution_cost_(g, h, row_in_master, col_in_master, exact_method);
189  }
190  else if (row_in_master < g.num_nodes()) {
191  master_problem(row_in_master, h.num_nodes()) = compute_deletion_cost_(g, row_in_master);
192  }
193  else if (col_in_master < h.num_nodes()) {
194  master_problem(g.num_nodes(), col_in_master) = compute_insertion_cost_(h, col_in_master);
195  }
196  }
197  delete exact_method;
198  }
199 }
200 
201 template<class UserNodeLabel, class UserEdgeLabel>
202 void
204 lsape_init_graph_(const GEDGraph & graph) {
205  build_subgraphs_(graph);
206 }
207 
208 template<class UserNodeLabel, class UserEdgeLabel>
211 exact_method_factory_() const {
212  GEDMethod<UserNodeLabel, UserEdgeLabel> * exact_method{nullptr};
213  if (exact_method_ == Options::GEDMethod::ANCHOR_AWARE_GED) {
215  }
216 #ifdef GUROBI
217  else if (exact_method_ == Options::GEDMethod::F1) {
218  exact_method = new F1<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
219  }
220  else if (exact_method_ == Options::GEDMethod::F2) {
221  exact_method = new F2<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
222  }
223  else if (exact_method_ == Options::GEDMethod::COMPACT_MIP) {
224  exact_method = new CompactMIP<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
225  }
226 #endif
227  if (exact_options_ == "") {
228  exact_method->set_options(std::string("--map-root-to-root TRUE --time-limit 0.0001 --threads ") + std::to_string(this->num_threads_));
229  }
230  else {
231  exact_method->set_options(exact_options_ + " --map-root-to-root TRUE --threads " + std::to_string(this->num_threads_));
232  }
233  return exact_method;
234 }
235 
236 template<class UserNodeLabel, class UserEdgeLabel>
237 void
239 lsape_pre_graph_init_(bool called_at_runtime) {
240  if (load_config_file_()) {
241  std::map<std::string, std::string> options;
242  util::parse_config_file(infile_, options);
243  depth_ = std::stod(options.at("depth"));
244  }
245  else {
246  depth_ = (min_depth_ + max_depth_) / 2;
247  }
248 }
249 
250 template<class UserNodeLabel, class UserEdgeLabel>
251 void
254 
255  // Return, if a configuration file is provided or if the minimal depth equals the maximal depth.
256  if (load_config_file_() or (min_depth_ == max_depth_)) {
257  return;
258  }
259 
260  // Find the best depth.
261  std::size_t num_runs{(max_depth_ - min_depth_ + 1) * (this->ged_data_.num_graphs() * this->ged_data_.num_graphs())};
262  ProgressBar progress_bar(num_runs);
263  std::cout << "\r" << progress_bar << std::flush;
264  double best_avg_ub{std::numeric_limits<double>::infinity()};
265  std::size_t best_depth{min_depth_};
266  LSAPESolver lsape_solver;
267  lsape_solver.set_model(this->lsape_model_);
268  for (depth_ = min_depth_; depth_ <= max_depth_; depth_++) {
269  for (auto graph = this->ged_data_.begin(); graph != this->ged_data_.end(); graph++) {
270  build_subgraphs_(*graph);
271  }
272  double avg_ub{0.0};
273  for (auto g = this->ged_data_.begin(); g != this->ged_data_.end(); g++) {
274  if (this->ged_data_.is_shuffled_graph_copy(g->id())) {
275  continue;
276  }
277  for (auto h = this->ged_data_.begin(); h != this->ged_data_.end(); h++) {
278  if (this->ged_data_.is_shuffled_graph_copy(h->id())) {
279  continue;
280  }
281  NodeMap node_map(g->num_nodes(), h->num_nodes());
282  DMatrix lsape_instance(g->num_nodes() + 1, h->num_nodes() + 1, 0.0);
283  lsape_solver.set_problem(&lsape_instance);
284  if (this->ged_data_.shuffled_graph_copies_available() and (g->id() == h->id())) {
285  GEDGraph::GraphID id_shuffled_graph_copy{this->ged_data_.id_shuffled_graph_copy(h->id())};
286  lsape_populate_instance_(*g, this->ged_data_.graph(id_shuffled_graph_copy), lsape_instance);
287  lsape_solver.solve();
288  util::construct_node_map_from_solver(lsape_solver, node_map);
289  this->ged_data_.compute_induced_cost(*g, this->ged_data_.graph(id_shuffled_graph_copy), node_map);
290  }
291  else {
292  lsape_populate_instance_(*g, *h, lsape_instance);
293  lsape_solver.solve();
294  util::construct_node_map_from_solver(lsape_solver, node_map);
295  this->ged_data_.compute_induced_cost(*g, *h, node_map);
296  }
297  avg_ub += node_map.induced_cost();
298  progress_bar.increment();
299  std::cout << "\r" << progress_bar << std::flush;
300  }
301  }
302  if (avg_ub < best_avg_ub) {
303  best_avg_ub = avg_ub;
304  best_depth = depth_;
305  }
306  }
307  std::cout << "\n";
308  depth_ = best_depth;
309  for (auto graph = this->ged_data_.begin(); graph != this->ged_data_.end(); graph++) {
310  build_subgraphs_(*graph);
311  }
312 
313  // Save the found depth.
314  if (outfile_ != "") {
315  std::map<std::string, std::string> options;
316  options["depth"] = std::to_string(depth_);
317  util::save_as_config_file(outfile_, options);
318  }
319 }
320 
321 // === Definitions of private helper member functions. ===
322 template<class UserNodeLabel, class UserEdgeLabel>
323 bool
325 load_config_file_() const {
326  return (infile_ != "");
327 }
328 
329 template<class UserNodeLabel, class UserEdgeLabel>
330 double
333 
334  const GEDGraph & subgraph_i{subgraphs_.at(subgraph_id_(g, i))};
335  const GEDGraph & subgraph_k{subgraphs_.at(subgraph_id_(h, k))};
336  Result result;
337  exact_method->run_as_util(subgraph_i, subgraph_k, result);
338  return result.upper_bound();
339 }
340 
341 template<class UserNodeLabel, class UserEdgeLabel>
342 double
345  const GEDGraph & subgraph_i{subgraphs_.at(subgraph_id_(g, i))};
346  GEDGraph dummy_graph;
347  NodeMap matching(subgraph_i.num_nodes(), 0);
348  for (auto node = subgraph_i.nodes().first; node != subgraph_i.nodes().second; node++) {
349  matching.add_assignment(*node, GEDGraph::dummy_node());
350  }
351  this->ged_data_.compute_induced_cost(subgraph_i, dummy_graph, matching);
352  return matching.induced_cost();
353 }
354 
355 template<class UserNodeLabel, class UserEdgeLabel>
356 double
359  const GEDGraph & subgraph_k{subgraphs_.at(subgraph_id_(h, k))};
360  GEDGraph dummy_graph;
361  NodeMap matching(0, subgraph_k.num_nodes());
362  for (auto node = subgraph_k.nodes().first; node != subgraph_k.nodes().second; node++) {
363  matching.add_assignment(GEDGraph::dummy_node(), *node);
364  }
365  this->ged_data_.compute_induced_cost(dummy_graph, subgraph_k, matching);
366  return matching.induced_cost();
367 }
368 
369 template<class UserNodeLabel, class UserEdgeLabel>
370 void
372 build_subgraphs_(const GEDGraph & graph) {
373  for (auto node = graph.nodes().first; node != graph.nodes().second; node++) {
374  build_subgraph_(graph, *node);
375  }
376 }
377 
378 template<class UserNodeLabel, class UserEdgeLabel>
379 void
381 build_subgraph_(const GEDGraph & graph, GEDGraph::NodeID root_node) {
382  GEDGraph::GraphID subgraph_id{subgraph_id_(graph, root_node)};
383  subgraphs_[subgraph_id] = GEDGraph(subgraph_id);
384  GEDGraph::NodeID root_in_subgraph{subgraphs_.at(subgraph_id).add_node()};
385  subgraphs_.at(subgraph_id).set_label(root_in_subgraph, graph.get_node_label(root_node));
386  GEDGraph::NodeNodeMap ids_in_subgraph;
387  ids_in_subgraph[root_node] = root_in_subgraph;
388  build_subgraph_dfs_(graph, root_node, 0, ids_in_subgraph, subgraphs_.at(subgraph_id));
389  subgraphs_.at(subgraph_id).setup_adjacency_matrix();
390 }
391 
392 template<class UserNodeLabel, class UserEdgeLabel>
395 subgraph_id_(const GEDGraph & graph, GEDGraph::NodeID node) const {
396  return (this->ged_data_.max_num_nodes() * graph.id()) + node;
397 }
398 
399 template<class UserNodeLabel, class UserEdgeLabel>
400 void
402 build_subgraph_dfs_(const GEDGraph & graph, GEDGraph::NodeID current_node, std::size_t depth_current_node, GEDGraph::NodeNodeMap & ids_in_subgraph, GEDGraph & subgraph) {
403  if (depth_current_node >= depth_) {
404  return;
405  }
406  GEDGraph::NodeID current_node_in_subgraph{ids_in_subgraph.at(current_node)};
407  for (auto edge = graph.incident_edges(current_node).first; edge != graph.incident_edges(current_node).second; edge++) {
408  GEDGraph::NodeID next_node{graph.head(*edge)};
409  GEDGraph::NodeID next_node_in_subgraph;
410  bool found_new_node{false};
411  if (ids_in_subgraph.find(next_node) == ids_in_subgraph.end()) {
412  found_new_node = true;
413  next_node_in_subgraph = subgraph.add_node();
414  subgraph.set_label(next_node_in_subgraph, graph.get_node_label(next_node));
415  ids_in_subgraph[next_node] = next_node_in_subgraph;
416  }
417  else {
418  next_node_in_subgraph = ids_in_subgraph.at(next_node);
419  }
420  if (not subgraph.safe_is_edge(current_node_in_subgraph, next_node_in_subgraph)) {
421  GEDGraph::EdgeID next_edge{subgraph.add_edge(current_node_in_subgraph, next_node_in_subgraph)};
422  subgraph.set_label(next_edge, graph.get_edge_label(*edge));
423  }
424  if (found_new_node) {
425  build_subgraph_dfs_(graph, next_node, depth_current_node + 1, ids_in_subgraph, subgraph);
426  }
427  }
428 }
429 
430 }
431 
432 #endif /* SRC_METHODS_SUBGRAPH_IPP_ */
std::map< NodeID, NodeID > NodeNodeMap
Map that assigns node IDs to node IDs.
Definition: ged_graph.hpp:114
void set_model(const Model &model)
Makes the solver use a specific model for optimal solving.
std::size_t num_nodes() const
Returns the number of nodes.
Definition: ged_graph.ipp:211
virtual std::string lsape_valid_options_string_() const final
Returns string of all valid options that are not among the ones shared by all derived classes of ged:...
Definition: subgraph.ipp:70
EdgeID add_edge(NodeID tail, NodeID head)
Adds a new edge to the graph.
Definition: ged_graph.ipp:86
std::size_t num_threads_
The number of threads to be used.
double upper_bound() const
Returns the upper bound for GED.
Definition: result.ipp:51
This class solves LSAPE instances by calling the library lsape available at https://bougleux.users.greyc.fr/lsape/.
A class for node maps.
Definition: node_map.hpp:43
Mixed integer linear programming formulation of the graph edit distance.
Definition: f1.hpp:42
std::vector< GEDGraph >::size_type GraphID
Type of internally used graph IDs.
Definition: ged_graph.hpp:112
const GEDData< UserNodeLabel, UserEdgeLabel > & ged_data_
The data on which the method is run.
Definition: ged_method.hpp:124
NodeID head(EdgeID edge) const
Returns the head of an edge.
Definition: ged_graph.ipp:199
virtual void lsape_populate_instance_(const GEDGraph &g, const GEDGraph &h, DMatrix &master_problem) final
Populates the LSAPE instance.
Definition: subgraph.ipp:174
virtual bool lsape_parse_option_(const std::string &option, const std::string &arg) final
Parses one option that is not among the ones shared by all derived classes of ged::LSAPEBasedMethod.
Definition: subgraph.ipp:77
void run_as_util(const GEDGraph &g, const GEDGraph &h, Result &result)
Runs the method with options specified by set_options().
Definition: ged_method.ipp:129
void solve(int num_solutions=1)
Solves the LSAPE problem instance.
Selects ged::AnchorAwareGED.
A wrapper structure for the result of calls to ged::GEDMethod::run_as_util() and ged::GEDMethod::ged_...
Definition: result.hpp:38
Abstract class for the (suboptimal) computation of the graph edit distance.
Definition: ged_method.hpp:40
virtual void lsape_init_() final
Initializes the method after initializing the global variables for the graphs.
Definition: subgraph.ipp:253
std::size_t num_cols() const
Returns the number of columns.
Definition: matrix.ipp:110
Mixed integer linear programming formulation of the graph edit distance.
Definition: compact_mip.hpp:43
LabelID get_node_label(NodeID node) const
Returns the label of a given node.
Definition: ged_graph.ipp:126
Runtime error class.
Definition: error.hpp:37
static NodeID dummy_node()
Returns a dummy node.
Definition: ged_graph.ipp:56
void parse_config_file(const std::string &filename, std::map< std::string, std::string > &options)
Parses a configuration file.
Definition: misc.ipp:49
bool compute_lower_bound_
Flag that should be set to true if and only if the method computes a lower bound. ...
LSAPESolver::Model lsape_model_
Specifies model for optimal LSAPE solver.
LabelID get_edge_label(EdgeID edge) const
Returns the label of a given edge.
Definition: ged_graph.ipp:135
void set_options(const std::string &options)
Sets the options of the method.
Definition: ged_method.ipp:99
void set_label(NodeID node, LabelID label)
Sets a node&#39;s label.
Definition: ged_graph.ipp:114
std::pair< incident_edge_iterator, incident_edge_iterator > incident_edges(NodeID node) const
Provides access to all incident edges of a node.
Definition: ged_graph.ipp:150
A progress bar class.
std::pair< node_iterator, node_iterator > nodes() const
Provides access to all nodes in the graph.
Definition: ged_graph.ipp:205
The normalized input graphs used by GEDLIB. All labels are integers.
Definition: ged_graph.hpp:104
detail::GedGraphAL::edge_descriptor EdgeID
Internally used edge ID type.
Definition: ged_graph.hpp:110
virtual void lsape_init_graph_(const GEDGraph &graph) final
Initializes global variables for one graph.
Definition: subgraph.ipp:204
void add_assignment(GEDGraph::NodeID i, GEDGraph::NodeID k)
Add node substitution, insertion, or deletion to the node map.
Definition: node_map.ipp:183
Global namespace for GEDLIB.
NodeID add_node()
Adds a new vertex to the graph.
Definition: ged_graph.ipp:74
void increment()
Increments the number of solved tasks.
Computes upper bounds for general edit costs.
Definition: subgraph.hpp:49
Computes the exact graph edit distance for general edit costs.
void save_as_config_file(const std::string &filename, const std::map< std::string, std::string > &options)
Saves a string map as a configuration file as expected by parse_config_file().
Definition: misc.ipp:76
void construct_node_map_from_solver(const Solver &solver, NodeMap &node_map, std::size_t solution_id=0)
Constructs a node map from a solution to LSAPE or LSAPE stored in a ged::LSAPESolver or a ged::LSAPSo...
Definition: misc.ipp:86
void set_problem(const DMatrix *cost_matrix)
Sets the LSAPE problem instance.
virtual void lsape_set_default_options_() final
Sets all options that are not among the ones shared by all derived classes of ged::LSAPEBasedMethod t...
Definition: subgraph.ipp:57
std::size_t NodeID
Internally used vertex ID type.
Definition: ged_graph.hpp:108
virtual void lsape_pre_graph_init_(bool called_at_runtime) final
Initializes the method at runtime or during initialization before initializing the global variables f...
Definition: subgraph.ipp:239
Mixed integer linear programming formulation of the graph edit distance.
Definition: f2.hpp:42
GraphID id() const
Returns the ID of the graph.
Definition: ged_graph.ipp:184
bool safe_is_edge(NodeID tail, NodeID head) const
Checks if an edge exists.
Definition: ged_graph.ipp:272
std::size_t num_rows() const
Returns the number of rows.
Definition: matrix.ipp:85