27 #ifndef SRC_METHODS_SIMULATED_ANNEALING_IPP_ 28 #define SRC_METHODS_SIMULATED_ANNEALING_IPP_ 33 template<
class UserNodeLabel,
class UserEdgeLabel>
34 SimulatedAnnealing<UserNodeLabel, UserEdgeLabel>::
35 ~SimulatedAnnealing() {
37 delete lower_bound_method_;
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_(
""),
51 num_iterations_{1000},
52 start_probability_{0.8},
53 end_probability_{0.01} {}
56 template<
class UserNodeLabel,
class UserEdgeLabel>
62 lsape_method_->populate_instance_and_run_as_util(g, h, result, lsape_instance);
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));
72 if (lower_bound_method_ and (lower_bound_method_name_ != lsape_method_name_)) {
74 lower_bound_method_->run_as_util(g, h, lower_bound_result);
80 for (std::size_t pos{0}; pos < current_order.size(); pos++) {
81 current_order[pos] = pos;
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);
95 while ((best_node_map.induced_cost() > result.
lower_bound()) and (num_iterations++ < num_iterations_)) {
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())};
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;
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;
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);
118 temperature *= cooling_factor;
122 if (not (best_node_map == result.
node_maps().at(0))) {
129 template<
class UserNodeLabel,
class UserEdgeLabel>
133 lsape_method_->init();
134 if (lower_bound_method_) {
135 lower_bound_method_->init();
139 template<
class UserNodeLabel,
class UserEdgeLabel>
143 delete lsape_method_;
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;
153 start_probability_ = 0.8;
154 end_probability_ = 0.01;
157 template<
class UserNodeLabel,
class UserEdgeLabel>
161 return "[--threads <arg>] [--iterations <arg>] [--start-probability <arg>] [--end-probability <arg>] [--lower-bound-method <arg>] [--lsape-method <arg>] [--lsape-method-options <arg>]";
164 template<
class UserNodeLabel,
class UserEdgeLabel>
168 bool is_valid_option{
false};
169 if (option ==
"threads") {
171 num_threads_ = std::stoul(arg);
174 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>] [...]");
176 if (num_threads_ <= 0) {
177 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>] [...]");
179 if (lsape_method_options_ !=
"") {
180 lsape_method_options_ +=
" ";
182 lsape_method_options_ +=
"--threads " + std::to_string(num_threads_);
183 if (lower_bound_method_options_ !=
"") {
184 lower_bound_method_options_ +=
" ";
186 lower_bound_method_options_ +=
"--threads " + std::to_string(num_threads_);
187 is_valid_option =
true;
189 else if (option ==
"iterations") {
191 num_iterations_ = std::stoul(arg);
194 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option iterations. Usage: options = \"[--iterations <convertible to int greater 0>] [...]");
196 if (num_iterations_ <= 0) {
197 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option iterations. Usage: options = \"[--iterations <convertible to int greater 0>] [...]");
199 is_valid_option =
true;
201 else if (option ==
"start-probability") {
203 start_probability_ = std::stod(arg);
206 throw Error(std::string(
"Invalid argument ") + arg +
" for option start-probability. Usage: options = \"[--start-probability <convertible to double between 0 and 1>] [...]");
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>] [...]");
211 is_valid_option =
true;
213 else if (option ==
"end-probability") {
215 end_probability_ = std::stod(arg);
218 throw Error(std::string(
"Invalid argument ") + arg +
" for option end-probability. Usage: options = \"[--end-probability <convertible to double between 0 and 1>] [...]");
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>] [...]");
223 is_valid_option =
true;
225 else if (option ==
"lower-bound-method") {
226 lower_bound_method_name_ = arg;
227 if (arg ==
"BRANCH") {
230 else if (arg ==
"BRANCH_FAST") {
233 else if (arg ==
"BRANCH_TIGHT") {
235 if (lower_bound_method_options_ !=
"") {
236 lower_bound_method_options_ +=
" ";
238 lower_bound_method_options_ +=
"--upper-bound NO ";
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] [...]\"");
243 is_valid_option =
true;
245 else if (option ==
"lsape-method") {
246 lsape_method_name_ = arg;
247 if (arg ==
"BRANCH_FAST") {
250 else if (arg ==
"BRANCH_UNIFORM") {
253 else if (arg ==
"BRANCH") {
256 else if (arg ==
"NODE") {
259 else if (arg ==
"RING") {
262 else if (arg ==
"SUBGRAPH") {
265 else if (arg ==
"WALKS") {
268 else if (arg ==
"BIPARTITE_ML") {
271 else if (arg ==
"RINGE_ML") {
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] [...]");
277 is_valid_option =
true;
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);
289 lsape_method_options = lsape_method_options.substr(0, bad_option_start);
292 if (lsape_method_options_ !=
"" and lsape_method_options !=
"") {
293 lsape_method_options_ +=
" ";
295 lsape_method_options_ += lsape_method_options;
296 is_valid_option =
true;
298 if (lower_bound_method_) {
299 lower_bound_method_->set_options(lower_bound_method_options_);
301 lsape_method_->set_options(lsape_method_options_);
302 return is_valid_option;
306 template<
class UserNodeLabel,
class UserEdgeLabel>
310 std::vector<std::size_t> & candidate_order,
NodeMap & candidate_node_map)
const {
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);
322 candidate_order[0] = value_random_pos;
326 std::vector<bool> is_unassigned_col(h.
num_nodes(),
true);
327 for (std::size_t row : candidate_order) {
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))) {
336 is_unassigned_col[best_col] =
false;
341 for (std::size_t col{0}; col < h.
num_nodes(); col++) {
342 if (is_unassigned_col.at(col)) {
349 std::vector<bool> is_unassigned_row(g.
num_nodes(),
true);
350 for (std::size_t col : candidate_order) {
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))) {
359 is_unassigned_row[best_row] =
false;
364 for (std::size_t row{0}; row < g.
num_nodes(); row++) {
365 if (is_unassigned_row.at(row)) {
371 this->
ged_data_.compute_induced_cost(g, h, candidate_node_map);
Computes lower and upper bounds for general edit costs.
std::size_t num_nodes() const
Returns the number of nodes.
virtual void ged_run_(const GEDGraph &g, const GEDGraph &h, Result &result) final
Runs the method with options specified by set_options().
Computes an upper bound for general edit costs.
const GEDData< UserNodeLabel, UserEdgeLabel > & ged_data_
The data on which the method is run.
void set_lower_bound(double lower_bound)
Sets the lower bound for GED.
Uses characteristics of an LSAPE instance for defining feature vectors for node edit operations...
double lower_bound() const
Returns the lower bound for GED.
Computes lower and upper bounds for general edit costs.
std::vector< NodeMap > & node_maps()
Provides access to all node maps.
A wrapper structure for the result of calls to ged::GEDMethod::run_as_util() and ged::GEDMethod::ged_...
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.
static NodeID dummy_node()
Returns a dummy node.
Computes lower and upper bounds for general edit costs.
Computes lower and upper bounds for general edit costs.
virtual void ged_set_default_options_() final
Sets all options to default values.
The normalized input graphs used by GEDLIB. All labels are integers.
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.
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.
Computes an upper bound for general edit costs.
Uses LSAPE instances to approximate GED via simulated annealing.