27 #ifndef SRC_METHODS_LS_BASED_METHOD_IPP_ 28 #define SRC_METHODS_LS_BASED_METHOD_IPP_ 33 template<
class UserNodeLabel,
class UserEdgeLabel>
36 delete initialization_method_;
37 delete lower_bound_method_;
40 template<
class UserNodeLabel,
class UserEdgeLabel>
43 GEDMethod<UserNodeLabel, UserEdgeLabel>(ged_data),
45 initialization_method_{
nullptr},
46 initialization_options_(
""),
47 lower_bound_method_{
nullptr},
48 lower_bound_method_options_(
""),
49 random_substitution_ratio_{1.0},
50 num_initial_solutions_{1},
51 ratio_runs_from_initial_solutions_{1.0},
52 num_randpost_loops_{0},
53 max_randpost_retrials_{10},
54 randpost_penalty_{0.0},
57 use_real_randomness_{
true} {}
60 template<
class UserNodeLabel,
class UserEdgeLabel>
64 if (initialization_method_) {
65 initialization_method_->init();
67 if (lower_bound_method_) {
68 lower_bound_method_->init();
73 template<
class UserNodeLabel,
class UserEdgeLabel>
81 std::vector<NodeMap> initial_node_maps;
82 std::vector<NodeMap> result_node_maps;
83 std::vector<NodeMap> visited_node_maps;
84 double upper_bound{std::numeric_limits<double>::infinity()};
85 double lower_bound{0.0};
86 std::vector<std::vector<double>> counts_matrix(g.
num_nodes(), std::vector<double>(h.
num_nodes() + 1, 0.0));
87 double skewdness_counts_matrix{1.0};
88 generate_initial_node_maps_(g, h, initial_node_maps, result);
89 for (std::size_t node_map_id = 0; node_map_id < initial_node_maps.size(); node_map_id++) {
91 visited_node_maps.emplace_back(initial_node_maps.at(node_map_id));
96 if (lower_bound_method_) {
98 lower_bound_method_->run_as_util(g, h, lower_bound_result);
104 bool found_optimum{
false};
105 std::size_t num_runs_from_initial_solutions{num_runs_from_initial_solutions_()};
106 for (std::size_t loop{0}; loop <= num_randpost_loops_; loop++) {
111 for (
NodeMap & node_map : initial_node_maps) {
114 generate_node_maps_from_counts_matrix_(g,h,counts_matrix, visited_node_maps, initial_node_maps);
116 double former_upper_bound = upper_bound;
117 std::size_t terminated_runs{0};
118 std::vector<bool> is_converged_node_map(initial_node_maps.size(),
false);
121 #pragma omp parallel for if(num_threads_ > 1) schedule(dynamic) 123 for (std::size_t node_map_id = 0; node_map_id < initial_node_maps.size(); node_map_id++) {
124 if (not found_optimum and (terminated_runs < num_runs_from_initial_solutions)) {
128 is_converged_node_map[node_map_id] =
true;
129 upper_bound = std::min(upper_bound, result_node_maps.at(node_map_id).induced_cost());
130 found_optimum = (found_optimum or (result.
lower_bound() >= upper_bound));
135 if (logfile_name_!=
"" and loop !=0) {
136 std::ofstream result_file(logfile_name_.c_str(), std::ios_base::app);
137 result_file << g.
id() <<
"," << h.
id() <<
"," << loop <<
"," << skewdness_counts_matrix <<
"," << (former_upper_bound - upper_bound)/former_upper_bound <<
"\n" ;
140 if (not found_optimum and loop < num_randpost_loops_) {
141 skewdness_counts_matrix = update_counts_matrix_and_visited_node_maps_(result_node_maps, is_converged_node_map, upper_bound, lower_bound, visited_node_maps, loop, counts_matrix);
143 for (
NodeMap & node_map : result_node_maps) {
154 template<
class UserNodeLabel,
class UserEdgeLabel>
158 bool is_valid_option{
false};
159 if (option ==
"initialization-method") {
160 if (arg ==
"BIPARTITE_ML") {
163 else if (arg ==
"BIPARTITE") {
166 else if (arg ==
"BRANCH_FAST") {
169 else if (arg ==
"BRANCH_UNIFORM") {
172 else if (arg ==
"BRANCH") {
175 else if (arg ==
"NODE") {
178 else if (arg ==
"RING_ML") {
181 else if (arg ==
"RING") {
184 else if (arg ==
"SUBGRAPH") {
187 else if (arg ==
"WALKS") {
190 else if (arg !=
"RANDOM") {
191 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option initialization-method. Usage: options = \"[--initialization-method BIPARTITE_ML|BIPARTITE|BRANCH_FAST|BRANCH_UNIFORM|BRANCH|NODE|RING_ML|RING|SUBGRAPH|WALKS|RANDOM] [...]\"");
193 is_valid_option =
true;
195 else if (option ==
"randomness") {
196 if (arg ==
"PSEUDO") {
197 use_real_randomness_ =
false;
199 else if (arg !=
"REAL") {
200 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option randomness. Usage: options = \"[--randomness REAL|PSEUDO] [...]\"");
202 is_valid_option =
true;
204 else if (option ==
"initialization-options") {
205 std::string initialization_options(arg);
206 std::size_t bad_option_start{initialization_options.find(
"--threads")};
207 std::size_t next_option_start;
208 if (bad_option_start != std::string::npos) {
209 next_option_start = initialization_options.find(
"--", bad_option_start + 1);
210 if (next_option_start != std::string::npos) {
211 initialization_options = initialization_options.substr(0, bad_option_start) + initialization_options.substr(next_option_start);
214 initialization_options = initialization_options.substr(0, bad_option_start);
217 bad_option_start = initialization_options.find(
"--max-num-solutions");
218 if (bad_option_start != std::string::npos) {
219 initialization_options = initialization_options.substr(0, bad_option_start);
220 next_option_start = initialization_options.find(
"--", bad_option_start + 1);
221 if (next_option_start != std::string::npos) {
222 initialization_options = initialization_options.substr(0, bad_option_start) + initialization_options.substr(next_option_start);
225 initialization_options = initialization_options.substr(0, bad_option_start);
228 if (initialization_options_ !=
"") {
229 initialization_options_ +=
" ";
231 initialization_options_ += initialization_options;
232 is_valid_option =
true;
234 else if (option ==
"lower-bound-method") {
235 if (arg ==
"BRANCH") {
238 else if (arg ==
"BRANCH_FAST") {
241 else if (arg ==
"BRANCH_TIGHT") {
243 if (lower_bound_method_options_ !=
"") {
244 lower_bound_method_options_ +=
" ";
246 lower_bound_method_options_ +=
"--upper-bound NO ";
248 else if (arg !=
"NONE") {
249 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option lower-bound-method. Usage: options = \"[--lower-bound-method BRANCH|BRANCH_FAST|BRANCH_TIGHT|NONE] [...]\"");
251 is_valid_option =
true;
253 else if (option ==
"random-substitution-ratio") {
255 random_substitution_ratio_ = std::stod(arg);
258 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option random-substitution-ratio. Usage: options = \"[--random-substitution-ratio <convertible to double between 0 and 1>]\"");
260 if (random_substitution_ratio_ < 0 or random_substitution_ratio_ > 1) {
261 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option random-substitution-ratio. Usage: options = \"[--random-substitution-ratio <convertible to double between 0 and 1>]\"");
263 is_valid_option =
true;
265 else if (option ==
"log") {
267 is_valid_option =
true;
269 else if (option ==
"randpost-penalty") {
271 randpost_penalty_ = std::stod(arg);
274 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option randpost-penalty. Usage: options = \"[--randpost-penalty <convertible to double between 0 and 1>]\"");
276 if (randpost_penalty_ < 0 or randpost_penalty_ > 1) {
277 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option randpost-penalty. Usage: options = \"[--randpost-penalty <convertible to double between 0 and 1>]\"");
279 is_valid_option =
true;
281 else if (option ==
"randpost-decay") {
283 randpost_decay_ = std::stod(arg);
286 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option randpost-decay. Usage: options = \"[--randpost-decay <convertible to double between 0 and 1>]\"");
288 if (randpost_decay_ < 0 or randpost_decay_ > 1) {
289 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option randpost-decay. Usage: options = \"[--randpost-decay <convertible to double between 0 and 1>]\"");
291 is_valid_option =
true;
293 else if (option ==
"initial-solutions") {
295 num_initial_solutions_ = std::stoul(arg);
298 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option num-initial-solutions. Usage: options = \"[--initial-solutions <convertible to int greater 0>]\"");
300 if (num_initial_solutions_ <= 0) {
301 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option num-initial-solutions. Usage: options = \"[--initial-solutions <convertible to int greater 0>]\"");
303 if (initialization_options_ !=
"") {
304 initialization_options_ +=
" ";
306 initialization_options_ +=
"--max-num-solutions " + std::to_string(num_initial_solutions_);
307 is_valid_option =
true;
309 else if (option ==
"ratio-runs-from-initial-solutions") {
311 ratio_runs_from_initial_solutions_ = std::stod(arg);
314 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option ratio-runs-from-initial-solutions. Usage: options = \"[--ratio-runs-from-initial-solutions <convertible to double greater 0 and smaller equal 1>]\"");
316 if (ratio_runs_from_initial_solutions_ <= 0 or ratio_runs_from_initial_solutions_ > 1) {
317 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option ratio-runs-from-initial-solutions. Usage: options = \"[--ratio-runs-from-initial-solutions <convertible to double greater 0 and smaller equal 1>]\"");
319 is_valid_option =
true;
321 else if (option ==
"threads") {
326 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>]\"");
329 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>]\"");
331 if (initialization_options_ !=
"") {
332 initialization_options_ +=
" ";
334 initialization_options_ +=
"--threads " + std::to_string(
num_threads_);
335 if (lower_bound_method_options_ !=
"") {
336 lower_bound_method_options_ +=
" ";
338 lower_bound_method_options_ +=
"--threads " + std::to_string(
num_threads_);
339 is_valid_option =
true;
341 else if (option ==
"num-randpost-loops") {
343 num_randpost_loops_ = std::stoul(arg);
346 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option num-randpost-loops. Usage: options = \"[--num-randpost-loops <convertible to int greater equal 0>]\"");
348 is_valid_option =
true;
350 else if (option ==
"max-randpost-retrials") {
352 max_randpost_retrials_ = std::stoul(arg);
355 throw Error(std::string(
"Invalid argument \"") + arg +
"\" for option max-randpost-retrials. Usage: options = \"[--max-randpost-retrials <convertible to int greater equal 0>]\"");
357 is_valid_option =
true;
359 if (initialization_method_) {
360 initialization_method_->set_options(initialization_options_);
362 if (lower_bound_method_) {
363 lower_bound_method_->set_options(lower_bound_method_options_);
366 return is_valid_option;
369 template<
class UserNodeLabel,
class UserEdgeLabel>
374 return "[--randomness <arg>] [--log <arg>] [--initialization-method <arg>] [--initialization-options <arg>] [--random-substitution-ratio <arg>] [--initial-solutions <arg>] [--ratio-runs-from-initial-solutions <arg>] [--threads <arg>] [--num-randpost-loops <arg>] [--max-randpost-retrials <arg>] [--randpost-penalty <arg>] [--randpost-decay <arg>]";
376 return ls_valid_options_string_() +
"[--randomness <arg>] [--log <arg>] [--initialization-method <arg>] [--initialization-options <arg>] [--random-substitution-ratio <arg>] [--initial-solutions <arg>] [--ratio-runs-from-initial-solutions <arg>] [--threads <arg>] [--num-randpost-loops <arg>] [--max-randpost-retrials <arg>] [--randpost-penalty <arg>] [--randpost-decay <arg>]";
379 template<
class UserNodeLabel,
class UserEdgeLabel>
383 delete initialization_method_;
384 initialization_method_ =
nullptr;
385 initialization_options_ = std::string(
"");
386 delete lower_bound_method_;
387 lower_bound_method_ =
nullptr;
388 lower_bound_method_options_ = std::string(
"");
389 random_substitution_ratio_ = 1.0;
390 num_initial_solutions_ = 1;
391 ratio_runs_from_initial_solutions_ = 1.0;
393 num_randpost_loops_ = 0;
394 max_randpost_retrials_ = 10;
395 randpost_penalty_ = 0;
397 logfile_name_ = std::string(
"");
398 use_real_randomness_ =
true;
405 template<
class UserNodeLabel,
class UserEdgeLabel>
409 return static_cast<std::size_t
>(std::ceil(ratio_runs_from_initial_solutions_ * static_cast<double>(num_initial_solutions_)));
412 template<
class UserNodeLabel,
class UserEdgeLabel>
416 if (initialization_method_) {
417 generate_lsape_based_initial_node_maps_(g, h, initial_node_maps, result);
419 generate_random_initial_node_maps_(g, h, initial_node_maps);
422 template<
class UserNodeLabel,
class UserEdgeLabel>
427 initialization_method_->run_as_util(g, h, lsape_result);
428 initial_node_maps = lsape_result.
node_maps();
432 template<
class UserNodeLabel,
class UserEdgeLabel>
436 const double & lower_bound, std::vector<NodeMap> & visited_node_maps, std::size_t loop, std::vector<std::vector<double>> & counts_matrix)
const {
437 if (randpost_decay_ < 1) {
438 for (
auto & row : counts_matrix) {
439 for (
auto & cell : row) {
440 cell *= randpost_decay_;
444 std::size_t num_nodes_g{counts_matrix.size()};
445 std::size_t num_nodes_h{counts_matrix[0].size() - 1};
447 std::size_t node_map_id{0};
448 for (
const NodeMap & node_map : result_node_maps) {
449 if (not is_converged_node_map.at(node_map_id++)) {
453 k = node_map.image(i);
455 counts_matrix[i][k] += (1 - randpost_penalty_) + randpost_penalty_ * (upper_bound - lower_bound) / (node_map.induced_cost() - lower_bound);
458 counts_matrix[i][num_nodes_h]++;
461 visited_node_maps.emplace_back(node_map);
463 if (logfile_name_ ==
"") {
466 double skewdness{0.0};
467 std::size_t num_non_zeros_row{0};
468 std::size_t num_solutions = (loop + 1) * num_runs_from_initial_solutions_();
469 if (num_solutions == 1){
472 for (
const auto & row : counts_matrix) {
473 num_non_zeros_row = 0;
474 for (
const auto & cell : row) {
479 skewdness +=
static_cast<double>(num_solutions-num_non_zeros_row)/static_cast<double>(num_solutions-1);
481 return skewdness/
static_cast<double>(counts_matrix.size());
484 template<
class UserNodeLabel,
class UserEdgeLabel>
488 std::vector<GEDGraph::NodeID> permutation_g;
489 for (
auto node = g.
nodes().first; node != g.
nodes().second; node++) {
490 permutation_g.push_back(*node);
492 std::vector<GEDGraph::NodeID> permutation_h;
493 for (
auto node = h.
nodes().first; node != h.
nodes().second; node++) {
494 permutation_h.push_back(*node);
497 num_substituted_nodes = std::lround(num_substituted_nodes * random_substitution_ratio_);
500 if (use_real_randomness_) {
501 std::random_device rng_g;
502 urng_g.seed(rng_g());
503 std::random_device rng_h;
504 urng_h.seed(rng_h());
506 for (std::size_t counter{initial_node_maps.size()}; counter < num_initial_solutions_; counter++) {
507 std::shuffle(permutation_g.begin(), permutation_g.end(), urng_g);
508 std::shuffle(permutation_h.begin(), permutation_h.end(), urng_h);
510 for (
auto node = g.
nodes().first; node != g.
nodes().second; node++) {
513 for (
auto node = h.
nodes().first; node != h.
nodes().second; node++) {
516 for (std::size_t pos{0}; pos < num_substituted_nodes; pos++) {
517 initial_node_maps.back().add_assignment(permutation_g[pos], permutation_h[pos]);
519 this->
ged_data_.compute_induced_cost(g, h, initial_node_maps.back());
523 template<
class UserNodeLabel,
class UserEdgeLabel>
527 std::size_t num_nodes_g{counts_matrix.size()};
528 std::size_t num_nodes_h{counts_matrix[0].size() - 1};
530 for (
const auto & row : counts_matrix) {
531 for (
const auto & cell : row) {
532 max_count = std::max(max_count, cell);
536 if (use_real_randomness_) {
537 std::random_device rng;
540 std::size_t node_map_id{0};
541 std::size_t num_unsuccessful_trials{0};
542 while (node_map_id < initial_node_maps.size()) {
543 std::vector<std::vector<double>> temp_counts_matrix(counts_matrix);
546 if (max_randpost_retrials_ > 0 and num_unsuccessful_trials > 0) {
547 for (
auto & row : temp_counts_matrix) {
548 for (
auto & cell : row) {
549 cell += max_count *
static_cast<double>(num_unsuccessful_trials) / static_cast<double>(max_randpost_retrials_);
554 std::vector<bool> is_assigned_target_node(num_nodes_h,
false);
556 std::discrete_distribution<std::size_t> distribution(temp_counts_matrix[i].begin(), temp_counts_matrix[i].end());
559 bool is_all_zero{
true};
560 for (
const auto & cell : temp_counts_matrix[i]){
572 if (k < num_nodes_h) {
573 initial_node_maps.at(node_map_id).add_assignment(i, k);
574 is_assigned_target_node[k] =
true;
576 temp_counts_matrix[j][k] = 0.0;
585 if (not is_assigned_target_node[k]) {
592 if (num_unsuccessful_trials < max_randpost_retrials_) {
593 for (
auto visited_node_map = visited_node_maps.crbegin(); visited_node_map != visited_node_maps.crend(); visited_node_map++) {
594 if (*visited_node_map == initial_node_maps.at(node_map_id)) {
596 num_unsuccessful_trials++;
597 initial_node_maps.at(node_map_id).clear();
603 visited_node_maps.emplace_back(initial_node_maps.at(node_map_id));
604 this->
ged_data_.compute_induced_cost(g, h, initial_node_maps.at(node_map_id));
611 template<
class UserNodeLabel,
class UserEdgeLabel>
616 template<
class UserNodeLabel,
class UserEdgeLabel>
621 template<
class UserNodeLabel,
class UserEdgeLabel>
626 template<
class UserNodeLabel,
class UserEdgeLabel>
633 template<
class UserNodeLabel,
class UserEdgeLabel>
640 template<
class UserNodeLabel,
class UserEdgeLabel>
Computes lower and upper bounds for general edit costs.
std::size_t num_nodes() const
Returns the number of nodes.
Abstract class for methods that use variants of local search for upper bounding the graph edit distan...
Contains the standardized input data along with basic functionality.
virtual void ls_runtime_init_(const GEDGraph &g, const GEDGraph &h)
Initializes the method for a run between two graphs.
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_...
Abstract class for the (suboptimal) computation of the graph edit distance.
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 ...
Computes lower and upper bounds for general edit costs.
LSBasedMethod(const GEDData< UserNodeLabel, UserEdgeLabel > &ged_data)
Constructor.
virtual std::string ged_valid_options_string_() const final
Returns string of all valid options.
Uses ring structures for defining feature vectors for node edit operations.
virtual bool ls_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::LSBasedMethod.
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.
virtual ~LSBasedMethod()=0
Pure virtual destructor.
static NodeID dummy_node()
Returns a dummy node.
Computes lower and upper bounds for general edit costs.
virtual void ged_run_(const GEDGraph &g, const GEDGraph &h, Result &result) final
Runs the method with options specified by set_options().
Computes lower and upper bounds for general edit costs.
std::size_t num_threads_
The number of threads to be used.
virtual void ls_init_()
Initializes the method.
virtual std::string ls_valid_options_string_() const
Returns string of all valid options that are not among the ones shared by all derived classes of ged:...
std::pair< node_iterator, node_iterator > nodes() const
Provides access to all nodes in the graph.
virtual void ged_init_() final
Initializes the method.
The normalized input graphs used by GEDLIB. All labels are integers.
Global namespace for GEDLIB.
Computes upper bounds for general edit costs.
Computes an upper bound for general edit costs.
virtual void ged_set_default_options_() final
Sets all options to default values.
virtual void ls_run_from_initial_solution_(const GEDGraph &g, const GEDGraph &h, double lower_bound, const NodeMap &initial_node_map, NodeMap &output_node_map)
Runs the local search from an initial node map.
std::size_t NodeID
Internally used vertex ID type.
GraphID id() const
Returns the ID of the graph.
virtual bool ged_parse_option_(const std::string &option, const std::string &arg) final
Parses one option.
virtual void ls_set_default_options_()
Sets all options that are not among the ones shared by all derived classes of ged::LSBasedMethod to d...