27 #ifndef SRC_METHODS_SUBGRAPH_IPP_ 28 #define SRC_METHODS_SUBGRAPH_IPP_ 34 template<
class UserNodeLabel,
class UserEdgeLabel>
35 Subgraph<UserNodeLabel, UserEdgeLabel>::
38 template<
class UserNodeLabel,
class UserEdgeLabel>
39 Subgraph<UserNodeLabel, UserEdgeLabel>::
40 Subgraph(
const GEDData<UserNodeLabel, UserEdgeLabel> & ged_data) :
41 LSAPEBasedMethod<UserNodeLabel, UserEdgeLabel>(ged_data),
54 template<
class UserNodeLabel,
class UserEdgeLabel>
61 infile_ = std::string(
"");
62 outfile_ = std::string(
"");
64 exact_options_ = std::string(
"");
67 template<
class UserNodeLabel,
class UserEdgeLabel>
71 return "[--depth-range <arg>] [--load <arg>] [--save <arg>] [--subproblem-solver <arg>] [--subproblem-solver-options <arg>]";
74 template<
class UserNodeLabel,
class UserEdgeLabel>
78 bool is_valid_option{
false};
79 if (option ==
"load") {
81 is_valid_option =
true;
83 else if (option ==
"save") {
85 is_valid_option =
true;
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,
',')) {
92 min_depth_ = std::stoi(min_depth);
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>] [...]");
98 max_depth_ = std::stoi(max_depth);
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>] [...]");
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>] [...]");
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>] [...]");
110 is_valid_option =
true;
112 else if (option ==
"subproblem-solver") {
115 exact_method_ = Options::GEDMethod::F1;
117 else if (arg ==
"F2") {
118 exact_method_ = Options::GEDMethod::F2;
120 else if (arg ==
"COMPACT_MIP") {
121 exact_method_ = Options::GEDMethod::COMPACT_MIP;
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] [...]\"");
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] [...]\"");
131 is_valid_option =
true;
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);
143 exact_options_ = exact_options_.substr(0, bad_option_start);
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);
153 exact_options_ = exact_options_.substr(0, bad_option_start);
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);
163 exact_options_ = exact_options_.substr(0, bad_option_start);
166 is_valid_option =
true;
168 return is_valid_option;
171 template<
class UserNodeLabel,
class UserEdgeLabel>
178 #pragma omp parallel for if(this->num_threads_ > 1) 180 for (std::size_t row_in_master = 0; row_in_master < master_problem.
num_rows(); row_in_master++) {
184 exact_method = exact_method_factory_();
186 for (std::size_t col_in_master = 0; col_in_master < master_problem.
num_cols(); col_in_master++) {
188 master_problem(row_in_master, col_in_master) = compute_substitution_cost_(g, h, row_in_master, col_in_master, exact_method);
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);
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);
201 template<
class UserNodeLabel,
class UserEdgeLabel>
205 build_subgraphs_(graph);
208 template<
class UserNodeLabel,
class UserEdgeLabel>
217 else if (exact_method_ == Options::GEDMethod::F1) {
220 else if (exact_method_ == Options::GEDMethod::F2) {
223 else if (exact_method_ == Options::GEDMethod::COMPACT_MIP) {
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_));
231 exact_method->set_options(exact_options_ +
" --map-root-to-root TRUE --threads " + std::to_string(this->
num_threads_));
236 template<
class UserNodeLabel,
class UserEdgeLabel>
240 if (load_config_file_()) {
241 std::map<std::string, std::string> options;
243 depth_ = std::stod(options.at(
"depth"));
246 depth_ = (min_depth_ + max_depth_) / 2;
250 template<
class UserNodeLabel,
class UserEdgeLabel>
256 if (load_config_file_() or (min_depth_ == max_depth_)) {
261 std::size_t num_runs{(max_depth_ - min_depth_ + 1) * (this->
ged_data_.num_graphs() * this->
ged_data_.num_graphs())};
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_};
268 for (depth_ = min_depth_; depth_ <= max_depth_; depth_++) {
270 build_subgraphs_(*graph);
274 if (this->
ged_data_.is_shuffled_graph_copy(g->id())) {
278 if (this->
ged_data_.is_shuffled_graph_copy(h->id())) {
281 NodeMap node_map(g->num_nodes(), h->num_nodes());
282 DMatrix lsape_instance(g->num_nodes() + 1, h->num_nodes() + 1, 0.0);
284 if (this->
ged_data_.shuffled_graph_copies_available() and (g->id() == h->id())) {
287 lsape_solver.
solve();
289 this->
ged_data_.compute_induced_cost(*g, this->
ged_data_.graph(id_shuffled_graph_copy), node_map);
293 lsape_solver.
solve();
295 this->
ged_data_.compute_induced_cost(*g, *h, node_map);
297 avg_ub += node_map.induced_cost();
299 std::cout <<
"\r" << progress_bar << std::flush;
302 if (avg_ub < best_avg_ub) {
303 best_avg_ub = avg_ub;
310 build_subgraphs_(*graph);
314 if (outfile_ !=
"") {
315 std::map<std::string, std::string> options;
316 options[
"depth"] = std::to_string(depth_);
322 template<
class UserNodeLabel,
class UserEdgeLabel>
326 return (infile_ !=
"");
329 template<
class UserNodeLabel,
class UserEdgeLabel>
334 const GEDGraph & subgraph_i{subgraphs_.at(subgraph_id_(g, i))};
335 const GEDGraph & subgraph_k{subgraphs_.at(subgraph_id_(h, k))};
337 exact_method->
run_as_util(subgraph_i, subgraph_k, result);
341 template<
class UserNodeLabel,
class UserEdgeLabel>
345 const GEDGraph & subgraph_i{subgraphs_.at(subgraph_id_(g, i))};
347 NodeMap matching(subgraph_i.num_nodes(), 0);
348 for (
auto node = subgraph_i.nodes().first; node != subgraph_i.nodes().second; node++) {
351 this->
ged_data_.compute_induced_cost(subgraph_i, dummy_graph, matching);
352 return matching.induced_cost();
355 template<
class UserNodeLabel,
class UserEdgeLabel>
359 const GEDGraph & subgraph_k{subgraphs_.at(subgraph_id_(h, k))};
361 NodeMap matching(0, subgraph_k.num_nodes());
362 for (
auto node = subgraph_k.nodes().first; node != subgraph_k.nodes().second; node++) {
365 this->
ged_data_.compute_induced_cost(dummy_graph, subgraph_k, matching);
366 return matching.induced_cost();
369 template<
class UserNodeLabel,
class UserEdgeLabel>
373 for (
auto node = graph.
nodes().first; node != graph.
nodes().second; node++) {
374 build_subgraph_(graph, *node);
378 template<
class UserNodeLabel,
class UserEdgeLabel>
383 subgraphs_[subgraph_id] =
GEDGraph(subgraph_id);
385 subgraphs_.at(subgraph_id).set_label(root_in_subgraph, graph.
get_node_label(root_node));
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();
392 template<
class UserNodeLabel,
class UserEdgeLabel>
396 return (this->
ged_data_.max_num_nodes() * graph.
id()) + node;
399 template<
class UserNodeLabel,
class UserEdgeLabel>
403 if (depth_current_node >= depth_) {
406 GEDGraph::NodeID current_node_in_subgraph{ids_in_subgraph.at(current_node)};
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();
415 ids_in_subgraph[next_node] = next_node_in_subgraph;
418 next_node_in_subgraph = ids_in_subgraph.at(next_node);
420 if (not subgraph.
safe_is_edge(current_node_in_subgraph, next_node_in_subgraph)) {
424 if (found_new_node) {
425 build_subgraph_dfs_(graph, next_node, depth_current_node + 1, ids_in_subgraph, subgraph);
std::map< NodeID, NodeID > NodeNodeMap
Map that assigns node IDs to node IDs.
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.
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:...
EdgeID add_edge(NodeID tail, NodeID head)
Adds a new edge to the graph.
std::size_t num_threads_
The number of threads to be used.
double upper_bound() const
Returns the upper bound for GED.
This class solves LSAPE instances by calling the library lsape available at https://bougleux.users.greyc.fr/lsape/.
Mixed integer linear programming formulation of the graph edit distance.
std::vector< GEDGraph >::size_type GraphID
Type of internally used graph IDs.
const GEDData< UserNodeLabel, UserEdgeLabel > & ged_data_
The data on which the method is run.
NodeID head(EdgeID edge) const
Returns the head of an edge.
virtual void lsape_populate_instance_(const GEDGraph &g, const GEDGraph &h, DMatrix &master_problem) final
Populates the LSAPE instance.
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.
void run_as_util(const GEDGraph &g, const GEDGraph &h, Result &result)
Runs the method with options specified by set_options().
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_...
Abstract class for the (suboptimal) computation of the graph edit distance.
virtual void lsape_init_() final
Initializes the method after initializing the global variables for the graphs.
std::size_t num_cols() const
Returns the number of columns.
Mixed integer linear programming formulation of the graph edit distance.
LabelID get_node_label(NodeID node) const
Returns the label of a given node.
static NodeID dummy_node()
Returns a dummy node.
void parse_config_file(const std::string &filename, std::map< std::string, std::string > &options)
Parses a configuration file.
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.
void set_options(const std::string &options)
Sets the options of the method.
void set_label(NodeID node, LabelID label)
Sets a node's label.
std::pair< incident_edge_iterator, incident_edge_iterator > incident_edges(NodeID node) const
Provides access to all incident edges of a node.
std::pair< node_iterator, node_iterator > nodes() const
Provides access to all nodes in the graph.
The normalized input graphs used by GEDLIB. All labels are integers.
detail::GedGraphAL::edge_descriptor EdgeID
Internally used edge ID type.
virtual void lsape_init_graph_(const GEDGraph &graph) final
Initializes global variables for one graph.
void add_assignment(GEDGraph::NodeID i, GEDGraph::NodeID k)
Add node substitution, insertion, or deletion to the node map.
Global namespace for GEDLIB.
NodeID add_node()
Adds a new vertex to the graph.
void increment()
Increments the number of solved tasks.
Computes upper bounds for general edit costs.
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().
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...
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...
std::size_t NodeID
Internally used vertex ID type.
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...
Mixed integer linear programming formulation of the graph edit distance.
GraphID id() const
Returns the ID of the graph.
bool safe_is_edge(NodeID tail, NodeID head) const
Checks if an edge exists.
std::size_t num_rows() const
Returns the number of rows.