GEDLIB  1.0
mip_based_method.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_MIP_BASED_METHOD_IPP_
28 #define SRC_METHODS_MIP_BASED_METHOD_IPP_
29 
30 namespace ged {
31 
32 // === Definitions of destructor and constructor. ===
33 template<class UserNodeLabel, class UserEdgeLabel>
36 
37 template<class UserNodeLabel, class UserEdgeLabel>
40 GEDMethod<UserNodeLabel, UserEdgeLabel>(ged_data),
41 relax_{false},
42 map_root_to_root_{true},
43 num_threads_{1},
44 time_limit_in_sec_{0},
45 tune_{false},
46 tune_time_limit_in_sec_{0},
47 lsape_model_{LSAPESolver::ECBP},
48 project_to_node_map_{true} {}
49 
50 // === Definitions of member functions inherited from GEDMethod. ===
51 template<class UserNodeLabel, class UserEdgeLabel>
52 void
55  mip_init_();
56 }
57 
58 template<class UserNodeLabel, class UserEdgeLabel>
59 void
61 ged_run_(const GEDGraph & g, const GEDGraph & h, Result & result) {
62  try {
63  // Initialize optimization environment.
64  GRBEnv env = GRBEnv();
65  env.set(GRB_IntParam_OutputFlag, 0);
66  env.set(GRB_IntParam_Threads, num_threads_);
67  if (time_limit_in_sec_ > 0) {
68  env.set(GRB_DoubleParam_TimeLimit, time_limit_in_sec_);
69  }
70  if (tune_ and (tune_time_limit_in_sec_ > 0)) {
71  env.set(GRB_DoubleParam_TuneTimeLimit, tune_time_limit_in_sec_);
72  }
73 
74  // Populate and verify the model.
75  GRBModel model = GRBModel(env);
76  mip_populate_model_(g, h, model);
77  if (model.get(GRB_IntAttr_IsMIP) and relax_) {
78  throw Error("Relaxed model contains integral variables.");
79  }
80 
81  // Tune and solve the model.
82  if (tune_) {
83  model.tune();
84  }
85  model.optimize();
86 
87  // Translate the solved model into a result.
88  int model_status{model.get(GRB_IntAttr_Status)};
89  if (model_status == GRB_INF_OR_UNBD) {
90  throw Error("The model is infeasible or unbounded.");
91  }
92  else if (model_status == GRB_UNBOUNDED) {
93  throw Error("The model is unbounded.");
94  }
95  else if (model_status == GRB_INFEASIBLE) {
96  model.computeIIS();
97  model.write("infeasible_model.lp");
98  model.write("infeasible_model.ilp");
99  throw Error("The model for the graphs with IDs " + std::to_string(g.id()) + " and " + std::to_string(h.id()) + " is infeasible. For more information, investigate files 'infeasible_model.lp' and 'infeasible_model.ilp'.");
100  }
101  else if ((model_status == GRB_OPTIMAL) or (model_status == GRB_SUBOPTIMAL) or (model_status == GRB_TIME_LIMIT)) {
102  if (model_status == GRB_OPTIMAL) {
103  result.set_lower_bound(model.get(GRB_DoubleAttr_ObjVal));
104  }
105  if (not relax_) {
106  result.add_node_map(g.num_nodes(), h.num_nodes());
107  mip_model_to_node_map_(g, h, model, result.node_map(0));
108  if (map_root_to_root_ and (result.node_map(0).image(0) != 0)) {
109  throw Error("Root constrained model does not map root to root.");
110  }
111  }
112  else if (project_to_node_map_) {
113  DMatrix lsape_instance(g.num_nodes() + 1, h.num_nodes() + 1, 1);
114  if (mip_model_to_lsape_projection_problem_(g, h, model, lsape_instance)) {
115  LSAPESolver lsape_solver(&lsape_instance);
116  lsape_solver.set_model(lsape_model_);
117  lsape_solver.solve();
118  result.add_node_map(g.num_nodes(), h.num_nodes());
119  util::construct_node_map_from_solver(lsape_solver, result.node_map(0));
120  this->ged_data_.compute_induced_cost(g, h, result.node_map(0));
121  }
122  }
123  }
124  else {
125  throw Error("Unexpected model status " + std::to_string(model_status) + ".");
126  }
127  }
128  catch (const GRBException & e) {
129  throw Error("Optimization failed with GRBException. Error code: " + std::to_string(e.getErrorCode()) + ". Error message: " + e.getMessage() + ".");
130  }
131 }
132 
133 template<class UserNodeLabel, class UserEdgeLabel>
134 bool
136 ged_parse_option_(const std::string & option, const std::string & arg) {
137  bool is_valid_option{false};
138  if (option == "relax") {
139  if (arg == "TRUE") {
140  relax_ = true;
141  }
142  else if (arg != "FALSE") {
143  throw Error(std::string("Invalid argument \"") + arg + "\" for option relax. Usage: options = \"[--relax TRUE|FALSE] [...]\"");
144  }
145  is_valid_option = true;
146  }
147  else if (option == "project-to-node-map") {
148  if (arg == "FALSE") {
149  project_to_node_map_ = false;
150  }
151  else if (arg != "TRUE") {
152  throw Error(std::string("Invalid argument \"") + arg + "\" for option project-to-node-map. Usage: options = \"[--project-to-node-map TRUE|FALSE] [...]\"");
153  }
154  is_valid_option = true;
155  }
156  else if (option == "lsape-model") {
157  if (arg == "EBP") {
158  lsape_model_ = LSAPESolver::EBP;
159  }
160  else if (arg == "FLWC") {
161  lsape_model_ = LSAPESolver::FLWC;
162  }
163  else if (arg == "FLCC") {
164  lsape_model_ = LSAPESolver::FLCC;
165  }
166  else if (arg == "FBP") {
167  lsape_model_ = LSAPESolver::FBP;
168  }
169  else if (arg == "SFBP") {
170  lsape_model_ = LSAPESolver::SFBP;
171  }
172  else if (arg == "FBP0") {
173  lsape_model_ = LSAPESolver::FBP0;
174  }
175  else if (arg != "ECBP") {
176  throw ged::Error(std::string("Invalid argument ") + arg + " for option lsape-model. Usage: options = \"[--lsape-model ECBP|EBP|FLWC|FLCC|FBP|SFBP|FBP0] [...]\"");
177  }
178  is_valid_option = true;
179  }
180  else if (option == "map-root-to-root") {
181  if (arg == "TRUE") {
182  map_root_to_root_ = true;
183  }
184  else if (arg != "FALSE") {
185  throw Error(std::string("Invalid argument \"") + arg + "\" for option map-root-to-root. Usage: options = \"[--map-root-to-root TRUE|FALSE] [...]\"");
186  }
187  is_valid_option = true;
188  }
189  else if (option == "tune") {
190  if (arg == "TRUE") {
191  tune_ = true;
192  }
193  else if (arg != "FALSE") {
194  throw Error(std::string("Invalid argument \"") + arg + "\" for option tune. Usage: options = \"[--tune TRUE|FALSE] [...]\"");
195  }
196  is_valid_option = true;
197  }
198  else if (option == "threads") {
199  try {
200  num_threads_ = std::stoul(arg);
201  }
202  catch (...) {
203  throw Error(std::string("Invalid argument \"") + arg + "\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>]\"");
204  }
205  if (num_threads_ <= 0) {
206  throw Error(std::string("Invalid argument \"") + arg + "\" for option threads. Usage: options = \"[--threads <convertible to int greater 0>]\"");
207  }
208  is_valid_option = true;
209  }
210  else if (option == "time-limit") {
211  try {
212  time_limit_in_sec_ = std::stod(arg);
213  }
214  catch (...) {
215  throw Error(std::string("Invalid argument \"") + arg + "\" for option time-limit. Usage: options = \"[--time-limit <convertible to double>] [...]");
216  }
217  is_valid_option = true;
218  }
219  else if (option == "tune-time-limit") {
220  try {
221  tune_time_limit_in_sec_ = std::stod(arg);
222  }
223  catch (...) {
224  throw Error(std::string("Invalid argument \"") + arg + "\" for option tune-time-limit. Usage: options = \"[--tune-time-limit <convertible to double>] [...]");
225  }
226  is_valid_option = true;
227  }
228  is_valid_option = is_valid_option or mip_parse_option_(option, arg);
229  return is_valid_option;
230 }
231 
232 template<class UserNodeLabel, class UserEdgeLabel>
233 std::string
236  if (mip_valid_options_string_() == "") {
237  return "[--relax <arg>] [--map-root-to-root <arg>] [--tune <arg>] [--threads <arg>] [--time-limit <arg>] [--tune-time-limit <arg>] [--lsape-model <arg>]";
238  }
239  return mip_valid_options_string_() + " [--relax <arg>] [--project-to-node-map <arg>] [--map-root-to-root <arg>] [--tune <arg>] [--threads <arg>] [--time-limit <arg>] [--tune-time-limit <arg>] [--lsape-model <arg>]";
240 }
241 
242 template<class UserNodeLabel, class UserEdgeLabel>
243 void
246  relax_ = false;
247  project_to_node_map_ = true;
248  map_root_to_root_ = false;
249  tune_ = false;
250  num_threads_ = 1;
251  time_limit_in_sec_ = 0;
252  tune_time_limit_in_sec_ = 0;
253  lsape_model_ = LSAPESolver::ECBP;
255 }
256 
257 // Default definitions of virtual member functions.
258 
259 template<class UserNodeLabel, class UserEdgeLabel>
260 void
262 mip_populate_model_(const GEDGraph & g, const GEDGraph & h, GRBModel & model) {}
263 
264 template<class UserNodeLabel, class UserEdgeLabel>
265 void
267 mip_model_to_node_map_(const GEDGraph & g, const GEDGraph & h, GRBModel & model, NodeMap & node_map) {}
268 
269 template<class UserNodeLabel, class UserEdgeLabel>
270 bool
272 mip_model_to_lsape_projection_problem_(const GEDGraph & g, const GEDGraph & h, GRBModel & model, DMatrix & lsape_instance) {
273  return false;
274 }
275 
276 template<class UserNodeLabel, class UserEdgeLabel>
277 void
280 
281 template<class UserNodeLabel, class UserEdgeLabel>
282 bool
284 mip_parse_option_(const std::string & option, const std::string & arg) {
285  return false;
286 }
287 
288 template<class UserNodeLabel, class UserEdgeLabel>
289 std::string
292  return "";
293 }
294 
295 template<class UserNodeLabel, class UserEdgeLabel>
296 void
299 
300 }
301 
302 
303 
304 #endif /* SRC_METHODS_MIP_BASED_METHOD_IPP_ */
virtual void mip_init_()
Initializes the method.
virtual void ged_init_() final
Initializes the method.
void set_model(const Model &model)
Makes the solver use a specific model for optimal solving.
virtual bool mip_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::MIPBasedMethod.
virtual void mip_set_default_options_()
Sets all options that are not among the ones shared by all derived classes of ged::MIPBasedMethod to ...
bool relax_
If true, the model populated by mip_populate_model_() must be continuous.
Reduction to compact LSAP instance for quasimetric costs.
Reduction to compact LSAP instance for quasimetric costs.
virtual std::string mip_valid_options_string_() const
Returns string of all valid options that are not among the ones shared by all derived classes of ged:...
std::size_t num_nodes() const
Returns the number of nodes.
Definition: ged_graph.ipp:211
Contains the standardized input data along with basic functionality.
Definition: ged_data.hpp:55
This class solves LSAPE instances by calling the library lsape available at https://bougleux.users.greyc.fr/lsape/.
Reduction to compact LSAP instance without cost constraints.
A class for node maps.
Definition: node_map.hpp:43
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
virtual void mip_populate_model_(const GEDGraph &g, const GEDGraph &h, GRBModel &model)
Runs the local search from an initial node map.
virtual std::string ged_valid_options_string_() const final
Returns string of all valid options.
MIPBasedMethod(const GEDData< UserNodeLabel, UserEdgeLabel > &ged_data)
Constructor.
void solve(int num_solutions=1)
Solves the LSAPE problem instance.
A wrapper structure for the result of calls to ged::GEDMethod::run_as_util() and ged::GEDMethod::ged_...
Definition: result.hpp:38
virtual void ged_set_default_options_() final
Sets all options to default values.
Abstract class for the (suboptimal) computation of the graph edit distance.
Definition: ged_method.hpp:40
Reduction to extended LSAP instance without cost constraints.
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.
Definition: result.ipp:60
Runtime error class.
Definition: error.hpp:37
virtual ~MIPBasedMethod()=0
Pure virtual destructor.
Adaption of Hungarian Algorithm to LSAPE.
virtual void ged_run_(const GEDGraph &g, const GEDGraph &h, Result &result) final
Runs the method with options specified by set_options().
The normalized input graphs used by GEDLIB. All labels are integers.
Definition: ged_graph.hpp:104
Reduction to compact LSAP instance for quasimetric costs.
Global namespace for GEDLIB.
virtual void mip_model_to_node_map_(const GEDGraph &g, const GEDGraph &h, GRBModel &model, NodeMap &node_map)
Given a, possibly sub-optimally, solved unrelaxed model, this method constructs a node map and sets i...
GEDGraph::NodeID image(GEDGraph::NodeID node) const
Returns image of a node.
Definition: node_map.ipp:110
NodeMap & node_map(std::size_t index_node_map)
Provides access to a node map.
Definition: result.ipp:74
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...
Definition: misc.ipp:86
Reduction to compact LSAP instance for quasimetric costs.
virtual bool mip_model_to_lsape_projection_problem_(const GEDGraph &g, const GEDGraph &h, GRBModel &model, DMatrix &lsape_instance)
Given a, possibly sub-optimally, solved model, this method constructs an LSAPE instance for projectin...
virtual bool ged_parse_option_(const std::string &option, const std::string &arg) final
Parses one option.
GraphID id() const
Returns the ID of the graph.
Definition: ged_graph.ipp:184
bool map_root_to_root_
If true, the model populated by mip_populate_model_() must enforce that the nodes with ID 0 are mappe...