GEDLIB  1.0
simulated_annealing.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_SIMULATED_ANNEALING_IPP_
28 #define SRC_METHODS_SIMULATED_ANNEALING_IPP_
29 
30 namespace ged {
31 
32 // === Definitions of constructor and destructor. ===
33 template<class UserNodeLabel, class UserEdgeLabel>
34 SimulatedAnnealing<UserNodeLabel, UserEdgeLabel>::
35 ~SimulatedAnnealing() {
36  delete lsape_method_;
37  delete lower_bound_method_;
38 }
39 
40 template<class UserNodeLabel, class UserEdgeLabel>
41 SimulatedAnnealing<UserNodeLabel, UserEdgeLabel>::
42 SimulatedAnnealing(const GEDData<UserNodeLabel, UserEdgeLabel> & ged_data) :
43 GEDMethod<UserNodeLabel, UserEdgeLabel>(ged_data),
44 lsape_method_{new Bipartite<UserNodeLabel, UserEdgeLabel>(this->ged_data_)},
45 lsape_method_name_("BIPARTITE"),
46 lsape_method_options_(""),
47 lower_bound_method_{nullptr},
48 lower_bound_method_name_("NONE"),
49 lower_bound_method_options_(""),
50 num_threads_{1},
51 num_iterations_{1000},
52 start_probability_{0.8},
53 end_probability_{0.01} {}
54 
55 // === Definitions of member functions inherited from GEDMethod. ===
56 template<class UserNodeLabel, class UserEdgeLabel>
57 void
59 ged_run_(const GEDGraph & g, const GEDGraph & h, Result & result) {
60 
61  DMatrix lsape_instance;
62  lsape_method_->populate_instance_and_run_as_util(g, h, result, lsape_instance);
63 
64  // Initialize meta parameters.
65  double temperature{1.0/std::log(start_probability_)};
66  double cooling_factor{1};
67  if (num_iterations_ > 1) {
68  cooling_factor = std::pow((1.0/std::log(end_probability_)) / temperature, 1.0 / static_cast<double>(num_iterations_ - 1));
69  }
70 
71  // Initialize lower bound.
72  if (lower_bound_method_ and (lower_bound_method_name_ != lsape_method_name_)) {
73  Result lower_bound_result;
74  lower_bound_method_->run_as_util(g, h, lower_bound_result);
75  result.set_lower_bound(std::max(result.lower_bound(), lower_bound_result.lower_bound()));
76  }
77 
78  // Initialize the order for the candidate generator.
79  std::vector<std::size_t> current_order(std::max(g.num_nodes(), h.num_nodes()));
80  for (std::size_t pos{0}; pos < current_order.size(); pos++) {
81  current_order[pos] = pos;
82  }
83 
84  // Initialize variables.
85  double delta_sum{0};
86  NodeMap best_node_map(result.node_maps().at(0));
87  NodeMap current_node_map(best_node_map);
88  std::size_t num_iterations{0};
89  std::size_t num_iterations_without_improvement{0};
90  std::random_device rng;
91  std::mt19937 urng(rng());
92  std::uniform_real_distribution<double> distr(0, 1);
93 
94  // Main loop.
95  while ((best_node_map.induced_cost() > result.lower_bound()) and (num_iterations++ < num_iterations_)) {
96  NodeMap candidate_node_map(g.num_nodes(), h.num_nodes());
97  std::vector<std::size_t> candidate_order;
98  generate_candidate_(g, h, lsape_instance, current_order, candidate_order, candidate_node_map);
99  double delta{std::fabs(candidate_node_map.induced_cost() - current_node_map.induced_cost())};
100  delta_sum += delta;
101  double delta_avg{delta_sum / static_cast<double>(num_iterations)};
102  double random_number{distr(urng)};
103  if ((candidate_node_map.induced_cost() < current_node_map.induced_cost()) or (random_number < std::exp((-delta) / (delta_avg * temperature)))) {
104  current_node_map = candidate_node_map;
105  current_order = candidate_order;
106  }
107  if (current_node_map.induced_cost() < best_node_map.induced_cost()) {
108  best_node_map = current_node_map;
109  num_iterations_without_improvement = 0;
110  }
111  else {
112  num_iterations_without_improvement++;
113  random_number = distr(urng);
114  if (random_number < static_cast<double>(num_iterations_without_improvement) / static_cast<double>(num_iterations_)) {
115  std::shuffle(current_order.begin(), current_order.end(), urng);
116  }
117  }
118  temperature *= cooling_factor;
119  }
120 
121  // Store the best node map in the result if it has lead to an improvement.
122  if (not (best_node_map == result.node_maps().at(0))) {
123  result.node_maps().emplace(result.node_maps().begin(), best_node_map);
124  result.node_maps().pop_back();
125  }
126 
127 }
128 
129 template<class UserNodeLabel, class UserEdgeLabel>
130 void
133  lsape_method_->init();
134  if (lower_bound_method_) {
135  lower_bound_method_->init();
136  }
137 }
138 
139 template<class UserNodeLabel, class UserEdgeLabel>
140 void
143  delete lsape_method_;
144  lsape_method_ = new Bipartite<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
145  lsape_method_name_ = std::string("BIPARTITE");
146  lsape_method_options_ = std::string("");
147  delete lower_bound_method_;
148  lower_bound_method_ = nullptr;
149  lower_bound_method_name_ = std::string("NONE");
150  lower_bound_method_options_ = std::string("");
151  num_iterations_ = 1000;
152  num_threads_ = 1;
153  start_probability_ = 0.8;
154  end_probability_ = 0.01;
155 }
156 
157 template<class UserNodeLabel, class UserEdgeLabel>
158 std::string
161  return "[--threads <arg>] [--iterations <arg>] [--start-probability <arg>] [--end-probability <arg>] [--lower-bound-method <arg>] [--lsape-method <arg>] [--lsape-method-options <arg>]";
162 }
163 
164 template<class UserNodeLabel, class UserEdgeLabel>
165 bool
167 ged_parse_option_(const std::string & option, const std::string & arg) {
168  bool is_valid_option{false};
169  if (option == "threads") {
170  try {
171  num_threads_ = std::stoul(arg);
172  }
173  catch (...) {
174  throw Error(std::string("Invalid argument \"") + arg + "\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>] [...]");
175  }
176  if (num_threads_ <= 0) {
177  throw Error(std::string("Invalid argument \"") + arg + "\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>] [...]");
178  }
179  if (lsape_method_options_ != "") {
180  lsape_method_options_ += " ";
181  }
182  lsape_method_options_ += "--threads " + std::to_string(num_threads_);
183  if (lower_bound_method_options_ != "") {
184  lower_bound_method_options_ += " ";
185  }
186  lower_bound_method_options_ += "--threads " + std::to_string(num_threads_);
187  is_valid_option = true;
188  }
189  else if (option == "iterations") {
190  try {
191  num_iterations_ = std::stoul(arg);
192  }
193  catch (...) {
194  throw Error(std::string("Invalid argument \"") + arg + "\" for option iterations. Usage: options = \"[--iterations <convertible to int greater 0>] [...]");
195  }
196  if (num_iterations_ <= 0) {
197  throw Error(std::string("Invalid argument \"") + arg + "\" for option iterations. Usage: options = \"[--iterations <convertible to int greater 0>] [...]");
198  }
199  is_valid_option = true;
200  }
201  else if (option == "start-probability") {
202  try {
203  start_probability_ = std::stod(arg);
204  }
205  catch (...) {
206  throw Error(std::string("Invalid argument ") + arg + " for option start-probability. Usage: options = \"[--start-probability <convertible to double between 0 and 1>] [...]");
207  }
208  if (start_probability_ < 0.0 or start_probability_ > 1.0) {
209  throw Error(std::string("Invalid argument ") + arg + " for option start-probability. Usage: options = \"[--start-probability <convertible to double between 0 and 1>] [...]");
210  }
211  is_valid_option = true;
212  }
213  else if (option == "end-probability") {
214  try {
215  end_probability_ = std::stod(arg);
216  }
217  catch (...) {
218  throw Error(std::string("Invalid argument ") + arg + " for option end-probability. Usage: options = \"[--end-probability <convertible to double between 0 and 1>] [...]");
219  }
220  if (end_probability_ < 0.0 or end_probability_ > 1.0) {
221  throw Error(std::string("Invalid argument ") + arg + " for option end-probability. Usage: options = \"[--end-probability <convertible to double between 0 and 1>] [...]");
222  }
223  is_valid_option = true;
224  }
225  else if (option == "lower-bound-method") {
226  lower_bound_method_name_ = arg;
227  if (arg == "BRANCH") {
228  lower_bound_method_ = new Branch<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
229  }
230  else if (arg == "BRANCH_FAST") {
231  lower_bound_method_ = new BranchFast<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
232  }
233  else if (arg == "BRANCH_TIGHT") {
234  lower_bound_method_ = new BranchTight<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
235  if (lower_bound_method_options_ != "") {
236  lower_bound_method_options_ += " ";
237  }
238  lower_bound_method_options_ += "--upper-bound NO ";
239  }
240  else if (arg != "NONE") {
241  throw Error(std::string("Invalid argument \"") + arg + "\" for option lower-bound-method. Usage: options = \"[--lower-bound-method BRANCH|BRANCH_FAST|BRANCH_TIGHT|NONE] [...]\"");
242  }
243  is_valid_option = true;
244  }
245  else if (option == "lsape-method") {
246  lsape_method_name_ = arg;
247  if (arg == "BRANCH_FAST") {
248  lsape_method_ = new BranchFast<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
249  }
250  else if (arg == "BRANCH_UNIFORM") {
251  lsape_method_ = new BranchUniform<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
252  }
253  else if (arg == "BRANCH") {
254  lsape_method_ = new Branch<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
255  }
256  else if (arg == "NODE") {
257  lsape_method_ = new Node<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
258  }
259  else if (arg == "RING") {
260  lsape_method_ = new Ring<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
261  }
262  else if (arg == "SUBGRAPH") {
263  lsape_method_ = new Subgraph<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
264  }
265  else if (arg == "WALKS") {
266  lsape_method_ = new Walks<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
267  }
268  else if (arg == "BIPARTITE_ML") {
269  lsape_method_ = new BipartiteML<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
270  }
271  else if (arg == "RINGE_ML") {
272  lsape_method_ = new RingML<UserNodeLabel, UserEdgeLabel>(this->ged_data_);
273  }
274  else if (arg != "BIPARTITE") {
275  throw Error("Invalid argument \"" + arg + "\" for option lsape-method. Usage: options = \"[--lsape-method BIPARTITE|BRANCH_FAST|BRANCH_UNIFORM|BRANCH|NODE|RING|SUBGRAPH|WALKS|BIPARTITE_ML|RING_ML] [...]");
276  }
277  is_valid_option = true;
278  }
279  else if (option == "lsape-options") {
280  std::string lsape_method_options(arg);
281  std::size_t bad_option_start{lsape_method_options.find("--threads")};
282  std::size_t next_option_start;
283  if (bad_option_start != std::string::npos) {
284  next_option_start = lsape_method_options.find("--", bad_option_start + 1);
285  if (next_option_start != std::string::npos) {
286  lsape_method_options = lsape_method_options.substr(0, bad_option_start) + lsape_method_options.substr(next_option_start);
287  }
288  else {
289  lsape_method_options = lsape_method_options.substr(0, bad_option_start);
290  }
291  }
292  if (lsape_method_options_ != "" and lsape_method_options != "") {
293  lsape_method_options_ += " ";
294  }
295  lsape_method_options_ += lsape_method_options;
296  is_valid_option = true;
297  }
298  if (lower_bound_method_) {
299  lower_bound_method_->set_options(lower_bound_method_options_);
300  }
301  lsape_method_->set_options(lsape_method_options_);
302  return is_valid_option;
303 }
304 
305 // === Definitions of private helper member functions. ===
306 template<class UserNodeLabel, class UserEdgeLabel>
307 void
309 generate_candidate_(const GEDGraph & g, const GEDGraph & h, const DMatrix & lsape_instance, const std::vector<std::size_t> & current_order,
310  std::vector<std::size_t> & candidate_order, NodeMap & candidate_node_map) const {
311 
312  // Slightly change the order.
313  candidate_order = current_order;
314  std::random_device rng;
315  std::mt19937 urng(rng());
316  std::uniform_int_distribution<std::size_t> distr(0, candidate_order.size() - 1);
317  std::size_t random_pos{distr(urng)};
318  std::size_t value_random_pos{candidate_order.at(random_pos)};
319  for (std::size_t pos{random_pos}; pos >= 1; pos--) {
320  candidate_order[pos] = candidate_order.at(pos - 1);
321  }
322  candidate_order[0] = value_random_pos;
323 
324  // Greedily compute the node map.
325  if (g.num_nodes() >= h.num_nodes()) {
326  std::vector<bool> is_unassigned_col(h.num_nodes(), true);
327  for (std::size_t row : candidate_order) {
328  std::size_t best_col{h.num_nodes()};
329  for (std::size_t col{0}; col < h.num_nodes(); col++) {
330  if (is_unassigned_col.at(col) and (lsape_instance(row, col) < lsape_instance(row, best_col))) {
331  best_col = col;
332  }
333  }
334  if (best_col < h.num_nodes()) {
335  candidate_node_map.add_assignment(row, best_col);
336  is_unassigned_col[best_col] = false;
337  }
338  else {
339  candidate_node_map.add_assignment(row, GEDGraph::dummy_node());
340  }
341  for (std::size_t col{0}; col < h.num_nodes(); col++) {
342  if (is_unassigned_col.at(col)) {
343  candidate_node_map.add_assignment(GEDGraph::dummy_node(), col);
344  }
345  }
346  }
347  }
348  else {
349  std::vector<bool> is_unassigned_row(g.num_nodes(), true);
350  for (std::size_t col : candidate_order) {
351  std::size_t best_row{g.num_nodes()};
352  for (std::size_t row{0}; row < g.num_nodes(); row++) {
353  if (is_unassigned_row.at(row) and (lsape_instance(row, col) < lsape_instance(best_row, col))) {
354  best_row = row;
355  }
356  }
357  if (best_row < g.num_nodes()) {
358  candidate_node_map.add_assignment(best_row, col);
359  is_unassigned_row[best_row] = false;
360  }
361  else {
362  candidate_node_map.add_assignment(GEDGraph::dummy_node(), col);
363  }
364  for (std::size_t row{0}; row < g.num_nodes(); row++) {
365  if (is_unassigned_row.at(row)) {
366  candidate_node_map.add_assignment(row, GEDGraph::dummy_node());
367  }
368  }
369  }
370  }
371  this->ged_data_.compute_induced_cost(g, h, candidate_node_map);
372 }
373 
374 }
375 
376 #endif /* SRC_METHODS_SIMULATED_ANNEALING_IPP_ */
Computes lower and upper bounds for general edit costs.
Definition: branch.hpp:42
std::size_t num_nodes() const
Returns the number of nodes.
Definition: ged_graph.ipp:211
virtual void ged_run_(const GEDGraph &g, const GEDGraph &h, Result &result) final
Runs the method with options specified by set_options().
A class for node maps.
Definition: node_map.hpp:43
Computes an upper bound for general edit costs.
Definition: walks.hpp:47
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
Uses characteristics of an LSAPE instance for defining feature vectors for node edit operations...
double lower_bound() const
Returns the lower bound for GED.
Definition: result.ipp:45
Computes lower and upper bounds for general edit costs.
Definition: node.hpp:42
std::vector< NodeMap > & node_maps()
Provides access to all node maps.
Definition: result.ipp:98
A wrapper structure for the result of calls to ged::GEDMethod::run_as_util() and ged::GEDMethod::ged_...
Definition: result.hpp:38
Computes lower and upper bounds for general edit costs.
virtual void ged_init_() final
Initializes the method.
Uses ring structures for defining feature vectors for node edit operations.
Definition: ring_ml.hpp:48
Runtime error class.
Definition: error.hpp:37
static NodeID dummy_node()
Returns a dummy node.
Definition: ged_graph.ipp:56
Computes lower and upper bounds for general edit costs.
Definition: bipartite.hpp:42
Computes lower and upper bounds for general edit costs.
Definition: branch_fast.hpp:45
virtual void ged_set_default_options_() final
Sets all options to default values.
The normalized input graphs used by GEDLIB. All labels are integers.
Definition: ged_graph.hpp:104
virtual bool ged_parse_option_(const std::string &option, const std::string &arg) final
Parses one option.
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.
virtual std::string ged_valid_options_string_() const final
Returns string of all valid options.
Computes upper bounds for general edit costs.
Definition: subgraph.hpp:49
Computes an upper bound for general edit costs.
Definition: ring.hpp:51
Computes lower and upper bounds for uniform edit costs.
Uses LSAPE instances to approximate GED via simulated annealing.