GEDLIB  1.0
lsape_based_method.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_LSAPE_BASED_METHOD_IPP_
28 #define SRC_METHODS_LSAPE_BASED_METHOD_IPP_
29 
30 namespace ged {
31 
32 // === Definitions of destructor and constructor. ===
33 template<class UserNodeLabel, class UserEdgeLabel>
36 
37 template<class UserNodeLabel, class UserEdgeLabel>
40 GEDMethod<UserNodeLabel, UserEdgeLabel>(ged_data),
41 lsape_model_{LSAPESolver::ECBP},
44 solve_optimally_{true},
45 num_threads_{1},
46 centrality_method_{NONE},
47 centrality_weight_{0.7},
48 centralities_(),
49 max_num_solutions_{1} {}
50 
51 // === Definition of public member functions that extend the public interface provided by GEDMethod.
52 
53 template<class UserNodeLabel, class UserEdgeLabel>
54 void
56 populate_instance(const GEDGraph & g, const GEDGraph & h, DMatrix & lsape_instance) {
57  lsape_instance.resize(g.num_nodes() + 1, h.num_nodes() + 1);
58  if (not this->initialized_) {
60  init_graph_(g);
61  init_graph_(h);
63  }
64  lsape_populate_instance_(g, h, lsape_instance);
65  lsape_instance(g.num_nodes(), h.num_nodes()) = 0.0;
66 }
67 
68 template<class UserNodeLabel, class UserEdgeLabel>
69 void
71 populate_instance_and_run_as_util(const GEDGraph & g, const GEDGraph & h, Result & result, DMatrix & lsape_instance) {
72 
73  // Populate the LSAPE instance and set up the solver.
74  populate_instance(g, h, lsape_instance);
75  LSAPESolver lsape_solver(&lsape_instance);
76 
77  // Solve the LSAPE instance.
78  if (solve_optimally_) {
79  lsape_solver.set_model(lsape_model_);
80  }
81  else {
82  lsape_solver.set_greedy_method(greedy_method_);
83  }
84  lsape_solver.solve(max_num_solutions_);
85 
86  // Compute and store lower and upper bound
88  result.set_lower_bound(lsape_solver.minimal_cost() * lsape_lower_bound_scaling_factor_(g, h));
89  }
90 
91  for (std::size_t solution_id{0}; solution_id < lsape_solver.num_solutions(); solution_id++) {
92  std::size_t index_node_map{result.add_node_map(g.num_nodes(), h.num_nodes())};
93  util::construct_node_map_from_solver(lsape_solver, result.node_map(index_node_map), solution_id);
94  this->ged_data_.compute_induced_cost(g, h, result.node_map(index_node_map));
95  }
96 
97  // Add centralities and reoptimize
98  if (centrality_weight_ > 0.0 and centrality_method_ != NONE) {
99  add_centralities_(g, h, lsape_instance);
100  lsape_solver.solve(max_num_solutions_);
101  for (std::size_t solution_id{0}; solution_id < lsape_solver.num_solutions(); solution_id++) {
102  std::size_t index_node_map{result.add_node_map(g.num_nodes(), h.num_nodes())};
103  util::construct_node_map_from_solver(lsape_solver, result.node_map(index_node_map), solution_id);
104  this->ged_data_.compute_induced_cost(g, h, result.node_map(index_node_map));
105  }
106  }
107 
108  // Sort the node maps an set the upper bound.
109  result.sort_node_maps_and_set_upper_bound(max_num_solutions_);
110 }
111 
112 // === Definitions of member functions inherited from GEDMethod. ===
113 template<class UserNodeLabel, class UserEdgeLabel>
114 void
117  lsape_pre_graph_init_(false);
118  for (auto graph = this->ged_data_.begin(); graph != this->ged_data_.end(); graph++) {
119  init_graph_(*graph);
120  }
121  lsape_init_();
122 }
123 
124 template<class UserNodeLabel, class UserEdgeLabel>
125 void
127 ged_run_(const GEDGraph & g, const GEDGraph & h, Result & result) {
128  DMatrix lsape_instance;
129  populate_instance_and_run_as_util(g, h, result, lsape_instance);
130 }
131 
132 template<class UserNodeLabel, class UserEdgeLabel>
133 bool
135 ged_parse_option_(const std::string & option, const std::string & arg) {
136  bool is_valid_option{false};
137  if (option == "threads") {
138  try {
139  num_threads_ = std::stoul(arg);
140  }
141  catch (...) {
142  throw Error(std::string("Invalid argument \"") + arg + "\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>] [...]");
143  }
144  if (num_threads_ <= 0) {
145  throw Error(std::string("Invalid argument \"") + arg + "\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>] [...]");
146  }
147  is_valid_option = true;
148  }
149  else if (option == "lsape-model") {
150  if (arg == "EBP") {
152  }
153  else if (arg == "FLWC") {
155  }
156  else if (arg == "FLCC") {
158  }
159  else if (arg == "FBP") {
161  }
162  else if (arg == "SFBP") {
164  }
165  else if (arg == "FBP0") {
167  }
168  else if (arg == "ECBP") {
170  }
171  else {
172  throw ged::Error(std::string("Invalid argument ") + arg + " for option lsape-model. Usage: options = \"[--lsape-model ECBP|EBP|FLWC|FLCC|FBP|SFBP|FBP0] [...]\"");
173  }
174  is_valid_option = true;
175  }
176  else if (option == "greedy-method") {
177  if (arg == "BASIC") {
179  }
180  else if (arg == "REFINED") {
182  }
183  else if (arg == "LOSS") {
185  }
186  else if (arg == "BASIC_SORT") {
188  }
189  else if (arg == "INT_BASIC_SORT") {
191  }
192  else {
193  throw ged::Error(std::string("Invalid argument ") + arg + " for option greedy-method. Usage: options = \"[--greedy-method BASIC|REFINED|LOSS|BASIC_SORT|INT_BASIC_SORT] [...]\"");
194  }
195  is_valid_option = true;
196  }
197  else if (option == "optimal") {
198  if (arg == "TRUE") {
199  solve_optimally_ = true;
200  }
201  else if (arg == "FALSE") {
202  solve_optimally_ = false;
203  }
204  else {
205  throw ged::Error(std::string("Invalid argument ") + arg + " for option optimal. Usage: options = \"[--optimal TRUE|FALSE] [...]\"");
206  }
207  is_valid_option = true;
208  }
209  else if (option == "centrality-method") {
210  if (arg == "NONE") {
211  centrality_method_ = NONE;
212  }
213  else if (arg == "DEGREE") {
214  centrality_method_ = DEGREE;
215  }
216  else if (arg == "EIGENVECTOR") {
217  centrality_method_ = EIGENVECTOR;
218  }
219  else if (arg == "PAGERANK") {
220  centrality_method_ = PAGERANK;
221  }
222  else {
223  throw ged::Error(std::string("Invalid argument ") + arg + " for option centrality-method. Usage: options = \"[--centrality-method NONE|DEGREE|EIGENVECTOR|PAGERANK] [...]\"");
224  }
225  is_valid_option = true;
226  }
227  else if (option == "centrality-weight") {
228  try {
229  centrality_weight_ = std::stod(arg);
230  }
231  catch (...) {
232  throw Error(std::string("Invalid argument ") + arg + " for option centrality-weight. Usage: options = \"[--centrality-weight <convertible to double between 0 and 1>] [...]");
233  }
234  if (centrality_weight_ < 0.0 or centrality_weight_ > 1.0) {
235  throw Error(std::string("Invalid argument ") + arg + " for option centrality-weight. Usage: options = \"[--centrality-weight <convertible to double between 0 and 1>] [...]");
236  }
237  is_valid_option = true;
238  }
239  else if (option == "max-num-solutions") {
240  if (arg == "ALL") {
241  max_num_solutions_ = -1;
242  }
243  else {
244  try {
245  max_num_solutions_ = std::stoi(arg);
246  }
247  catch (...) {
248  throw Error(std::string("Invalid argument ") + arg + " for option max-num-solutions. Usage: options = \"[--max-num-solutions ALL|<convertible to int greater 0>] [...]");
249  }
250  if (max_num_solutions_ < 1) {
251  throw Error(std::string("Invalid argument ") + arg + " for option max-num-solutions. Usage: options = \"[--max-num-solutions ALL|<convertible to int greater 0>] [...]");
252  }
253  }
254  is_valid_option = true;
255  }
256  is_valid_option = is_valid_option or lsape_parse_option_(option, arg);
257  return is_valid_option;
258 }
259 
260 template<class UserNodeLabel, class UserEdgeLabel>
261 std::string
264  if (lsape_valid_options_string_() == "") {
265  return "[--lsape-model <arg>] [--threads <arg>] [--greedy-method <arg>] [--optimal <arg>] [--centrality-method <arg>] [--centrality-weight <arg>] [--max-num-solutions <arg>]";
266  }
267  return lsape_valid_options_string_() + " [--lsape-model <arg>] [--greedy-method <arg>] [--optimal <arg>] [--centrality-method <arg>] [--centrality-weight <arg>] [--max-num-solutions <arg>]";
268 }
269 
270 template<class UserNodeLabel, class UserEdgeLabel>
271 void
276  solve_optimally_ = true;
277  centrality_method_ = NONE;
278  centrality_weight_ = 0.7;
279  max_num_solutions_ = 1;
280  num_threads_ = 1;
282 }
283 
284 // === Definitions of private helper member functions. ===
285 template<class UserNodeLabel, class UserEdgeLabel>
286 void
288 init_graph_(const GEDGraph & graph) {
289  if (centrality_method_ != NONE) {
290  init_centralities_(graph);
291  }
292  lsape_init_graph_(graph);
293 }
294 
295 template<class UserNodeLabel, class UserEdgeLabel>
296 void
298 init_centralities_(const GEDGraph & graph) {
299  centralities_[graph.id()] = std::vector<double>(graph.num_nodes(), 0.0);
300  if (centrality_method_ == DEGREE) {
301  for (std::size_t row{0}; row < graph.num_nodes(); row++) {
302  centralities_.at(graph.id())[row] = static_cast<double>(graph.degree(row));
303  }
304  return;
305  }
306  DMatrix adj_matrix(graph.num_nodes(), graph.num_nodes());
307  util::init_adj_matrix(graph, adj_matrix);
308  double eigenvalue;
309  std::vector<double> eigenvector;
310  compute_eigenvector_with_largest_eigenvalue_(adj_matrix, eigenvector, eigenvalue);
311  for (std::size_t row{0}; row < adj_matrix.num_rows(); row++) {
312  for (std::size_t col{0}; col < adj_matrix.num_cols(); col++) {
313  double summand{adj_matrix(row, col) * eigenvector.at(col)};
314  if (centrality_method_ == PAGERANK) {
315  summand /= std::max(1.0, static_cast<double>(graph.degree(col)));
316  summand += 1.0;
317  }
318  centralities_.at(graph.id())[row] += summand;
319  }
320  if (centrality_method_ == PAGERANK) {
321  centralities_.at(graph.id())[row] *= 0.85;
322  }
323  else {
324  centralities_.at(graph.id())[row] /= eigenvalue;
325  }
326  }
327 }
328 
329 template<class UserNodeLabel, class UserEdgeLabel>
330 void
332 add_centralities_(const GEDGraph & g, const GEDGraph & h, DMatrix & master_problem) {
333  // substitution
334  master_problem *= (1 - centrality_weight_);
335 #ifdef _OPENMP
336  omp_set_num_threads(this->num_threads_ - 1);
337 #pragma omp parallel for if(this->num_threads_ > 1)
338 #endif
339  for (std::size_t row = 0; row < master_problem.num_rows(); row++) {
340  for (std::size_t col = 0; col < master_problem.num_cols(); col++) {
341  if (row < g.num_nodes() and col < h.num_nodes()) {
342  master_problem(row, col) += centrality_weight_ * std::fabs(static_cast<double>(centralities_.at(g.id()).at(row) - centralities_.at(h.id()).at(col)));
343  }
344  else if (row < g.num_nodes()) {
345  master_problem(row, h.num_nodes()) += centrality_weight_ * centralities_.at(g.id()).at(row);
346  }
347  else if (col < h.num_nodes()) {
348  master_problem(g.num_nodes(), col) += centrality_weight_ * centralities_.at(h.id()).at(col);
349  }
350  }
351  }
352 }
353 
354 template<class UserNodeLabel, class UserEdgeLabel>
355 void
357 compute_eigenvector_with_largest_eigenvalue_(const DMatrix & symmetric_matrix, std::vector<double> & eigenvector, double & eigenvalue) {
358  if (symmetric_matrix.num_rows() != symmetric_matrix.num_cols()) {
359  return;
360  }
361  Eigen::EigenSolver<Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic>> eigen_solver(symmetric_matrix.matrix());
362  Eigen::VectorXd eigen_values = eigen_solver.eigenvalues().real();
363  std::size_t pos;
364  eigenvalue = eigen_values.maxCoeff(&pos);
365  Eigen::VectorXd eigen_vector = eigen_solver.eigenvectors().col(pos).real();
366  eigenvector.resize(eigen_vector.rows());
367  for (std::size_t row{0}; row < static_cast<std::size_t>(eigen_vector.rows()); row++) {
368  eigenvector[row] = eigen_vector(row);
369  }
370 }
371 
372 // === Default definitions of private virtual member functions to be overridden by derived classes. ===
373 template<class UserNodeLabel, class UserEdgeLabel>
374 void
377 
378 template<class UserNodeLabel, class UserEdgeLabel>
379 bool
381 lsape_parse_option_(const std::string & option, const std::string & arg) {
382  return false;
383 }
384 
385 template<class UserNodeLabel, class UserEdgeLabel>
386 std::string
389  return "";
390 }
391 
392 template<class UserNodeLabel, class UserEdgeLabel>
393 void
396 
397 template<class UserNodeLabel, class UserEdgeLabel>
398 void
400 lsape_populate_instance_(const GEDGraph & g, const GEDGraph & h, DMatrix & master_problem) {}
401 
402 template<class UserNodeLabel, class UserEdgeLabel>
403 void
405 lsape_init_graph_(const GEDGraph & graph) {}
406 
407 template<class UserNodeLabel, class UserEdgeLabel>
408 void
410 lsape_pre_graph_init_(bool called_at_runtime) {}
411 
412 template<class UserNodeLabel, class UserEdgeLabel>
413 void
416 
417 template<class UserNodeLabel, class UserEdgeLabel>
418 double
421  return 1.0;
422 }
423 
424 }
425 
426 #endif /* SRC_METHODS_LSAPE_BASED_METHOD_IPP_ */
void set_model(const Model &model)
Makes the solver use a specific model for optimal solving.
void populate_instance_and_run_as_util(const GEDGraph &g, const GEDGraph &h, Result &result, DMatrix &lsape_instance)
Runs the method with options specified by set_options() and provides access to constructed LSAPE inst...
virtual bool lsape_parse_option_(const std::string &option, const std::string &arg)
Parses one option that is not among the ones shared by all derived classes of ged::LSAPEBasedMethod.
Reduction to compact LSAP instance for quasimetric costs.
virtual void lsape_set_default_options_()
Sets all options that are not among the ones shared by all derived classes of ged::LSAPEBasedMethod t...
Reduction to compact LSAP instance for quasimetric costs.
std::size_t num_nodes() const
Returns the number of nodes.
Definition: ged_graph.ipp:211
Contains the standardized input data along with basic functionality.
Definition: ged_data.hpp:55
virtual ~LSAPEBasedMethod()=0
Pure virtual destructor.
std::size_t num_threads_
The number of threads to be used.
This class solves LSAPE instances by calling the library lsape available at https://bougleux.users.greyc.fr/lsape/.
virtual std::string lsape_valid_options_string_() const
Returns string of all valid options that are not among the ones shared by all derived classes of ged:...
Reduction to compact LSAP instance without cost constraints.
void resize(std::size_t num_rows, std::size_t num_cols)
Resizes the matrix.
Definition: matrix.ipp:92
const GEDData< UserNodeLabel, UserEdgeLabel > & ged_data_
The data on which the method is run.
Definition: ged_method.hpp:124
void set_lower_bound(double lower_bound)
Sets the lower bound for GED.
Definition: result.ipp:39
void solve(int num_solutions=1)
Solves the LSAPE problem instance.
std::size_t degree(NodeID node) const
Returns node degree.
Definition: ged_graph.ipp:162
virtual double lsape_lower_bound_scaling_factor_(const GEDGraph &g, const GEDGraph &h)
Returns scaling factor for lower bound.
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
void sort_node_maps_and_set_upper_bound(std::size_t num_node_maps=std::numeric_limits< std::size_t >::max())
Sorts the vector of node maps w.r.t non-decreasing induced cost and possibly discards expensive node ...
Definition: result.ipp:110
Reduction to extended LSAP instance without cost constraints.
virtual void ged_set_default_options_() final
Sets all options to default values.
virtual void lsape_default_post_graph_init_()
Default initializes the method at runtime after initializing the global variables for the graphs...
std::size_t num_cols() const
Returns the number of columns.
Definition: matrix.ipp:110
bool solve_optimally_
Flag that equals true if an optimal LSAPE solver is used and false if a greedy method is employed...
virtual void ged_init_() final
Initializes the method.
std::size_t add_node_map(std::size_t num_nodes_g, std::size_t num_nodes_h)
Adds an empty node map to the result.
Definition: result.ipp:60
Runtime error class.
Definition: error.hpp:37
Adaption of Hungarian Algorithm to LSAPE.
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.
See https://doi.org/10.1007/978-3-319-18224-7_1.
Eigen::Matrix< ScalarT, Eigen::Dynamic, Eigen::Dynamic > & matrix()
Returns reference to the internal Eigen matrix.
Definition: matrix.ipp:228
Abstract class for methods that use lossy transformations to LSAPE for approximating the graph edit d...
double minimal_cost() const
Returns the cost of the computed solutions.
void populate_instance(const GEDGraph &g, const GEDGraph &h, DMatrix &lsape_instance)
Populates the LSAPE instance.
bool initialized_
A flag that equals true if init() has been called and false otherwise.
Definition: ged_method.hpp:119
The normalized input graphs used by GEDLIB. All labels are integers.
Definition: ged_graph.hpp:104
virtual void lsape_init_graph_(const GEDGraph &graph)
Initializes global variables for one graph.
Reduction to compact LSAP instance for quasimetric costs.
Global namespace for GEDLIB.
virtual void lsape_populate_instance_(const GEDGraph &g, const GEDGraph &h, DMatrix &lsape_instance)
Populates the LSAPE instance.
virtual void lsape_pre_graph_init_(bool called_at_runtime)
Initializes the method at runtime or during initialization before initializing the global variables f...
NodeMap & node_map(std::size_t index_node_map)
Provides access to a node map.
Definition: result.ipp:74
See https://doi.org/10.1007/978-3-319-18224-7_1.
See https://doi.org/10.1007/978-3-319-18224-7_1.
virtual std::string ged_valid_options_string_() const final
Returns string of all valid options.
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
virtual void ged_run_(const GEDGraph &g, const GEDGraph &h, Result &result) final
Runs the method with options specified by set_options().
LSAPESolver::GreedyMethod greedy_method_
Specifies method for greedy LSAPE solver.
Reduction to compact LSAP instance for quasimetric costs.
std::size_t num_solutions() const
Returns the number of solutions.
void init_adj_matrix(const GEDGraph &graph, DMatrix &adj_matrix)
Initalizes the adjacency matrix of a graph.
Definition: misc.ipp:35
virtual void lsape_init_()
Initializes the method after initializing the global variables for the graphs.
See https://doi.org/10.1007/978-3-319-18224-7_1.
void set_greedy_method(const GreedyMethod &greedy_method)
Makes the solver use a greedy method.
GraphID id() const
Returns the ID of the graph.
Definition: ged_graph.ipp:184
LSAPEBasedMethod(const GEDData< UserNodeLabel, UserEdgeLabel > &ged_data)
Constructor.
virtual bool ged_parse_option_(const std::string &option, const std::string &arg) final
Parses one option.
std::size_t num_rows() const
Returns the number of rows.
Definition: matrix.ipp:85
See https://doi.org/10.1007/978-3-319-18224-7_1.