Skip to content

Commit

Permalink
implemented HED
Browse files Browse the repository at this point in the history
  • Loading branch information
dblumenthal committed Oct 8, 2018
1 parent 1c07a27 commit c19f1fd
Show file tree
Hide file tree
Showing 6 changed files with 327 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/env/common_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ struct Options {
BIPARTITE_ML, //!< Selects ged::BipartiteML.
REFINE, //!< Selects ged::Refine.
BP_BEAM, //!< Selects ged::BPBeam.
SIMULATED_ANNEALING //!< Selects ged::SimulatedAnnealing.
SIMULATED_ANNEALING, //!< Selects ged::SimulatedAnnealing.
HED //!< Selects ged::HED.
};

/*!
Expand Down
3 changes: 3 additions & 0 deletions src/env/ged_env.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,9 @@ set_method(Options::GEDMethod method, const std::string & options) {
case Options::GEDMethod::SIMULATED_ANNEALING:
ged_method_ = new SimulatedAnnealing<UserNodeLabel, UserEdgeLabel>(ged_data_);
break;
case Options::GEDMethod::HED:
ged_method_ = new HED<UserNodeLabel, UserEdgeLabel>(ged_data_);
break;
#ifdef GUROBI
case Options::GEDMethod::F1:
ged_method_ = new F1<UserNodeLabel, UserEdgeLabel>(ged_data_);
Expand Down
6 changes: 4 additions & 2 deletions src/methods/all_methods.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@
// Declarations of concrete derived classes of GEDMethod.

#include "branch_tight.hpp" // Declares BranchTight. Dependencies: "ged_method.hpp".
#include "anchor_aware_ged.hpp" // Declares Exact. Dependencies: "ged_method.hpp".
#include "anchor_aware_ged.hpp" // Declares Exact. Dependencies: "ged_method.hpp".
#include "partition.hpp" // Declares Partition. Dependencies: "ged_method.hpp".
#include "hybrid.hpp" // Declares Hybrid. Dependencies: "ged_method.hpp", "partition.hpp".
#include "branch_compact.hpp" // Declares BranchCompact. Dependencies: "ged_method.hpp".
#include "hed.hpp" // Declares HED. Dependencies: "ged_method.hpp".
#include "simulated_annealing.hpp" // Declares SimulatedAnnealing. Dependencies: "ged_method.hpp", "lsape_based_method.hpp"

// Declarations of concrete derived classes of MIPBasedMethod.
Expand Down Expand Up @@ -91,10 +92,11 @@
// Definitions of concrete derived classes of GEDMethod.

#include "branch_tight.ipp" // Defines BranchTight. Dependencies: "branch_tight.hpp", "ged_method.hpp".
#include "anchor_aware_ged.ipp" // Defines Exact. Dependencies: "exact.hpp", "ged_method.hpp", "ipfp.hpp".
#include "anchor_aware_ged.ipp" // Defines Exact. Dependencies: "exact.hpp", "ged_method.hpp", "ipfp.hpp".
#include "partition.ipp" // Defines Partition. Dependencies: "partition.hpp", "ged_method.hpp".
#include "hybrid.ipp" // Defines Hybrid. Dependencies: "hybrid.hpp", "ged_method.hpp", "partition.hpp", "branch_uniform.hpp".
#include "branch_compact.ipp" // Defines BranchCompact. Dependencies: "branch_compact.hpp", "ged_method.hpp".
#include "hed.ipp" // Defines HED. Dependencies: "hed.hpp", "ged_method.hpp".
#include "simulated_annealing.ipp" // Defines SimulatedAnnealing. Dependencies: "simulated_annealing.hpp", "ged_method.hpp", "lsape_based_method.hpp", "bipartite.hpp", "branch_hpp", "branch_fast.hpp", "node.hpp", "ring.hpp", "subgraph.hpp", "walks.hpp".

// Definitions of concrete derived classes of MIPBasedMethod.
Expand Down
87 changes: 87 additions & 0 deletions src/methods/hed.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/***************************************************************************
* *
* Copyright (C) 2018 by David B. Blumenthal *
* *
* This file is part of GEDLIB. *
* *
* GEDLIB is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as published *
* by the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* GEDLIB is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with GEDLIB. If not, see <http://www.gnu.org/licenses/>. *
* *
***************************************************************************/

/*!
* @file hed.hpp
* @brief ged::HED class declaration.
*/

#ifndef SRC_METHODS_HED_HPP_
#define SRC_METHODS_HED_HPP_

namespace ged {

/*!
* @brief Computes a lower bound for general edit costs.
* @details Implements the Hausdorff Edit Distance (HED) suggested in:
* - A. Fischer, C. Y. Suen, V. Frinken, K. Riesen, and H. Bunke:
* &ldquo;Approximation of graph edit distance based on Hausdorff matching&rdquo;,
* https:://doi.org/10.1016/j.patcog.2014.07.015
*
* Supports the following options:
* | <tt>\--@<option@> @<arg@></tt> | modified parameter | default | more information |
* | ------------------------------ | ------------------ | -------- | ---------------- |
* | <tt>\--threads @<convertible to int greater 0@></tt> | number of threads | 1 | can be used by derived classes |
* | <tt>\--lsape-model ECBP\|EBP\|FLWC\|FLCC\|FBP\|SFBP\|FBP0</tt> | model for optimally solving LSAPE | @p ECBP | ged::LSAPESolver::Model |
*/
template<class UserNodeLabel, class UserEdgeLabel>
class HED : public GEDMethod<UserNodeLabel, UserEdgeLabel> {

public:

virtual ~HED();

HED(const GEDData<UserNodeLabel, UserEdgeLabel> & ged_data);

private:

LSAPESolver::Model lsape_model_;

std::size_t num_threads_;

// Inherited member functions from GEDMethod.

virtual void ged_run_(const GEDGraph & g, const GEDGraph & h, Result & result) final;

virtual bool ged_parse_option_(const std::string & option, const std::string & arg) final;

virtual std::string ged_valid_options_string_() const final;

virtual void ged_set_default_options_() final;

// Helper member functions.

void populate_instance_(const GEDGraph & g, const GEDGraph & h, DMatrix & lsape_instance) const;

double compute_substitution_cost_(const GEDGraph & g, const GEDGraph & h, GEDGraph::NodeID i, GEDGraph::NodeID k) const;

double compute_deletion_cost_(const GEDGraph & g, GEDGraph::NodeID i) const;

double compute_insertion_cost_(const GEDGraph & h, GEDGraph::NodeID k) const;
};

}





#endif /* SRC_METHODS_HED_HPP_ */
219 changes: 219 additions & 0 deletions src/methods/hed.ipp
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/***************************************************************************
* *
* Copyright (C) 2018 by David B. Blumenthal *
* *
* This file is part of GEDLIB. *
* *
* GEDLIB is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as published *
* by the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* GEDLIB is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with GEDLIB. If not, see <http://www.gnu.org/licenses/>. *
* *
***************************************************************************/

/*!
* @file hed.ipp
* @brief ged::HED class definition.
*/

#ifndef SRC_METHODS_HED_IPP_
#define SRC_METHODS_HED_IPP_

namespace ged {

// === Definitions of destructor and constructor. ===
template<class UserNodeLabel, class UserEdgeLabel>
HED<UserNodeLabel, UserEdgeLabel>::
~HED() {}

template<class UserNodeLabel, class UserEdgeLabel>
HED<UserNodeLabel, UserEdgeLabel>::
HED(const GEDData<UserNodeLabel, UserEdgeLabel> & ged_data) :
GEDMethod<UserNodeLabel, UserEdgeLabel>(ged_data),
lsape_model_{LSAPESolver::Model::ECBP},
num_threads_{1} {}

// === Definitions of member functions inherited from GEDMethod. ===
template<class UserNodeLabel, class UserEdgeLabel>
void
HED<UserNodeLabel, UserEdgeLabel>::
ged_run_(const GEDGraph & g, const GEDGraph & h, Result & result) {
DMatrix lsape_instance(g.num_nodes() + 1, h.num_nodes() + 1);
populate_instance_(g, h, lsape_instance);
double hed{0};
hed += lsape_instance.matrix().rowwise().minCoeff().sum();
hed += lsape_instance.matrix().colwise().minCoeff().sum();
result.set_lower_bound(hed);
}

template<class UserNodeLabel, class UserEdgeLabel>
bool
HED<UserNodeLabel, UserEdgeLabel>::
ged_parse_option_(const std::string & option, const std::string & arg) {
if (option == "threads") {
try {
num_threads_ = std::stoul(arg);
}
catch (...) {
throw Error(std::string("Invalid argument \"") + arg + "\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>] [...]");
}
if (num_threads_ <= 0) {
throw Error(std::string("Invalid argument \"") + arg + "\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>] [...]");
}
return true;
}
else if (option == "lsape-model") {
if (arg == "EBP") {
lsape_model_ = LSAPESolver::EBP;
}
else if (arg == "FLWC") {
lsape_model_ = LSAPESolver::FLWC;
}
else if (arg == "FLCC") {
lsape_model_ = LSAPESolver::FLCC;
}
else if (arg == "FBP") {
lsape_model_ = LSAPESolver::FBP;
}
else if (arg == "SFBP") {
lsape_model_ = LSAPESolver::SFBP;
}
else if (arg == "FBP0") {
lsape_model_ = LSAPESolver::FBP0;
}
else if (arg != "ECBP") {
throw ged::Error(std::string("Invalid argument ") + arg + " for option lsape-model. Usage: options = \"[--lsape-model ECBP|EBP|FLWC|FLCC|FBP|SFBP|FBP0] [...]\"");
}
return true;
}
return false;
}

template<class UserNodeLabel, class UserEdgeLabel>
std::string
HED<UserNodeLabel, UserEdgeLabel>::
ged_valid_options_string_() const {
return "[--lsape-model <arg>] [--threads <arg>]";
}

template<class UserNodeLabel, class UserEdgeLabel>
void
HED<UserNodeLabel, UserEdgeLabel>::
ged_set_default_options_() {
lsape_model_ = LSAPESolver::ECBP;
num_threads_ = 1;
}

// === Definitions of private helper member functions. ===
template<class UserNodeLabel, class UserEdgeLabel>
void
HED<UserNodeLabel, UserEdgeLabel>::
populate_instance_(const GEDGraph & g, const GEDGraph & h, DMatrix & lsape_instance) const {

#ifdef _OPENMP
omp_set_num_threads(this->num_threads_ - 1);
#pragma omp parallel for if(this->num_threads_ > 1)
#endif
for (std::size_t row_in_master = 0; row_in_master < lsape_instance.num_rows(); row_in_master++) {
for (std::size_t col_in_master = 0; col_in_master < lsape_instance.num_cols(); col_in_master++) {
if ((row_in_master < g.num_nodes()) and (col_in_master < h.num_nodes())) {
lsape_instance(row_in_master, col_in_master) = compute_substitution_cost_(g, h, row_in_master, col_in_master);
}
else if (row_in_master < g.num_nodes()) {
lsape_instance(row_in_master, h.num_nodes()) = compute_deletion_cost_(g, row_in_master);
}
else if (col_in_master < h.num_nodes()) {
lsape_instance(g.num_nodes(), col_in_master) = compute_insertion_cost_(h, col_in_master);
}
}
}
}

template<class UserNodeLabel, class UserEdgeLabel>
double
HED<UserNodeLabel, UserEdgeLabel>::
compute_substitution_cost_(const GEDGraph & g, const GEDGraph & h, GEDGraph::NodeID i, GEDGraph::NodeID k) const {
// Collect node substitution costs.
double cost{this->ged_data_.node_cost(g.get_node_label(i), h.get_node_label(k))};

// Initialize subproblem.
DMatrix subproblem(g.degree(i) + 1, h.degree(k) + 1);

// Collect edge deletion costs.
std::size_t j{0};
for (auto ij = g.incident_edges(i).first; ij != g.incident_edges(i).second; ij++, j++) {
subproblem(j, h.degree(k)) = this->ged_data_.edge_cost(g.get_edge_label(*ij), ged::dummy_label()) / 2.0;
}

// Collect edge insertion costs.
std::size_t l{0};
for (auto kl = h.incident_edges(k).first; kl != h.incident_edges(k).second; kl++, l++) {
subproblem(g.degree(i), l) = this->ged_data_.edge_cost(ged::dummy_label(), h.get_edge_label(*kl)) / 2.0;
}
j = 0;

// Collect edge relabelling costs.
for (auto ij = g.incident_edges(i).first; ij != g.incident_edges(i).second; ij++, j++) {
l = 0;
for (auto kl = h.incident_edges(k).first; kl != h.incident_edges(k).second; kl++, l++) {
subproblem(j, l) = this->ged_data_.edge_cost(g.get_edge_label(*ij), h.get_edge_label(*kl)) / 2.0;
}
}

// Solve subproblem.
LSAPESolver subproblem_solver(&subproblem);
subproblem_solver.set_model(this->lsape_model_);
subproblem_solver.solve();

// Update and return overall substitution cost.
cost += subproblem_solver.minimal_cost();
return cost / 2;
}

template<class UserNodeLabel, class UserEdgeLabel>
double
HED<UserNodeLabel, UserEdgeLabel>::
compute_deletion_cost_(const GEDGraph & g, GEDGraph::NodeID i) const {
// Collect node deletion cost.
double cost{this->ged_data_.node_cost(g.get_node_label(i), ged::dummy_label())};

// Collect edge deletion costs.
auto incident_edges_i = g.incident_edges(i);
for (auto ij = incident_edges_i.first; ij != incident_edges_i.second; ij++) {
cost += this->ged_data_.edge_cost(g.get_edge_label(*ij), ged::dummy_label()) / 2.0;
}

// Return overall deletion cost.
return cost;
}

template<class UserNodeLabel, class UserEdgeLabel>
double
HED<UserNodeLabel, UserEdgeLabel>::
compute_insertion_cost_(const GEDGraph & h, GEDGraph::NodeID k) const {
// Collect node insertion cost.
double cost{this->ged_data_.node_cost(ged::dummy_label(), h.get_node_label(k))};

// Collect edge insertion costs.
auto incident_edges_k = h.incident_edges(k);
for (auto kl = incident_edges_k.first; kl != incident_edges_k.second; kl++) {
cost += this->ged_data_.edge_cost(ged::dummy_label(), h.get_edge_label(*kl)) / 2.0;
}

// Return overall insertion cost.
return cost;
}

}



#endif /* SRC_METHODS_HED_IPP_ */
12 changes: 12 additions & 0 deletions tests/unit_tests/src/ged_env_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,18 @@ TEST_CASE("testing on MAO graphs") {
std::cout << "upper bound = " << env.get_upper_bound(g, h) << ", runtime = " << env.get_runtime(g, h) << "\n";
}

SECTION("HED") {
std::cout << "\n===running HED ===\n";
env.set_method(ged::Options::GEDMethod::HED, "--threads 1");
env.run_method(g, h);
std::cout << "\noptions = \"--threads 1\"\n";
std::cout << "lower bound = " << env.get_lower_bound(g, h) << ", runtime = " << env.get_runtime(g, h) << "\n";
env.set_method(ged::Options::GEDMethod::HED, "--threads 4");
env.run_method(g, h);
std::cout << "\noptions = \"--threads 4\"\n";
std::cout << "lower bound = " << env.get_lower_bound(g, h) << ", runtime = " << env.get_runtime(g, h) << "\n";
}

#ifdef GUROBI
SECTION("BLPNoEdgeLabels") {
std::cout << "\n===running BLPNoEdgeLabels ===\n";
Expand Down

0 comments on commit c19f1fd

Please sign in to comment.