Refactor library structure

This commit is contained in:
2024-03-08 22:20:38 +01:00
parent 5bec9d4d2f
commit 7534cba7e6
23 changed files with 22 additions and 152 deletions

8
pyclfs/CMakeLists.txt Normal file
View File

@@ -0,0 +1,8 @@
include_directories(
${Python3_INCLUDE_DIRS}
${TORCH_INCLUDE_DIRS}
${PyClassifiers_SOURCE_DIR}/lib/json/include
/usr/local/include
)
add_library(PyClassifiers ODTE.cc STree.cc SVC.cc RandomForest.cc XGBoost.cc PyClassifier.cc PyWrap.cc)
target_link_libraries(PyClassifiers ${Python3_LIBRARIES} "${TORCH_LIBRARIES}" ${LIBTORCH_PYTHON} Boost::boost Boost::python Boost::numpy)

24
pyclfs/ODTE.cc Normal file
View File

@@ -0,0 +1,24 @@
#include "ODTE.h"
namespace pywrap {
ODTE::ODTE() : PyClassifier("odte", "Odte")
{
validHyperparameters = { "n_jobs", "n_estimators", "random_state" };
}
int ODTE::getNumberOfNodes() const
{
return callMethodInt("get_nodes");
}
int ODTE::getNumberOfEdges() const
{
return callMethodInt("get_leaves");
}
int ODTE::getNumberOfStates() const
{
return callMethodInt("get_depth");
}
std::string ODTE::graph()
{
return callMethodString("graph");
}
} /* namespace pywrap */

17
pyclfs/ODTE.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef ODTE_H
#define ODTE_H
#include "nlohmann/json.hpp"
#include "PyClassifier.h"
namespace pywrap {
class ODTE : public PyClassifier {
public:
ODTE();
~ODTE() = default;
int getNumberOfNodes() const override;
int getNumberOfEdges() const override;
int getNumberOfStates() const override;
std::string graph();
};
} /* namespace pywrap */
#endif /* ODTE_H */

96
pyclfs/PyClassifier.cc Normal file
View File

@@ -0,0 +1,96 @@
#include "PyClassifier.h"
namespace pywrap {
namespace bp = boost::python;
namespace np = boost::python::numpy;
PyClassifier::PyClassifier(const std::string& module, const std::string& className, bool sklearn) : module(module), className(className), sklearn(sklearn), fitted(false)
{
// This id allows to have more than one instance of the same module/class
id = reinterpret_cast<clfId_t>(this);
pyWrap = PyWrap::GetInstance();
pyWrap->importClass(id, module, className);
}
PyClassifier::~PyClassifier()
{
pyWrap->clean(id);
}
np::ndarray tensor2numpy(torch::Tensor& X)
{
int m = X.size(0);
int n = X.size(1);
auto Xn = np::from_data(X.data_ptr(), np::dtype::get_builtin<float>(), bp::make_tuple(m, n), bp::make_tuple(sizeof(X.dtype()) * 2 * n, sizeof(X.dtype()) * 2), bp::object());
Xn = Xn.transpose();
return Xn;
}
std::pair<np::ndarray, np::ndarray> tensors2numpy(torch::Tensor& X, torch::Tensor& y)
{
int n = X.size(1);
auto yn = np::from_data(y.data_ptr(), np::dtype::get_builtin<int32_t>(), bp::make_tuple(n), bp::make_tuple(sizeof(y.dtype()) * 2), bp::object());
return { tensor2numpy(X), yn };
}
std::string PyClassifier::version()
{
if (sklearn) {
return pyWrap->sklearnVersion();
}
return pyWrap->version(id);
}
std::string PyClassifier::callMethodString(const std::string& method)
{
return pyWrap->callMethodString(id, method);
}
int PyClassifier::callMethodSumOfItems(const std::string& method) const
{
return pyWrap->callMethodSumOfItems(id, method);
}
int PyClassifier::callMethodInt(const std::string& method) const
{
return pyWrap->callMethodInt(id, method);
}
PyClassifier& PyClassifier::fit(torch::Tensor& X, torch::Tensor& y)
{
if (!fitted && hyperparameters.size() > 0) {
pyWrap->setHyperparameters(id, hyperparameters);
}
auto [Xn, yn] = tensors2numpy(X, y);
CPyObject Xp = bp::incref(bp::object(Xn).ptr());
CPyObject yp = bp::incref(bp::object(yn).ptr());
pyWrap->fit(id, Xp, yp);
fitted = true;
return *this;
}
PyClassifier& PyClassifier::fit(torch::Tensor& X, torch::Tensor& y, const std::vector<std::string>& features, const std::string& className, std::map<std::string, std::vector<int>>& states)
{
return fit(X, y);
}
torch::Tensor PyClassifier::predict(torch::Tensor& X)
{
int dimension = X.size(1);
auto Xn = tensor2numpy(X);
CPyObject Xp = bp::incref(bp::object(Xn).ptr());
PyObject* incoming = pyWrap->predict(id, Xp);
bp::handle<> handle(incoming);
bp::object object(handle);
np::ndarray prediction = np::from_object(object);
if (PyErr_Occurred()) {
PyErr_Print();
throw std::runtime_error("Error creating object for predict in " + module + " and class " + className);
}
int* data = reinterpret_cast<int*>(prediction.get_data());
std::vector<int> vPrediction(data, data + prediction.shape(0));
auto resultTensor = torch::tensor(vPrediction, torch::kInt32);
Py_XDECREF(incoming);
return resultTensor;
}
float PyClassifier::score(torch::Tensor& X, torch::Tensor& y)
{
auto [Xn, yn] = tensors2numpy(X, y);
CPyObject Xp = bp::incref(bp::object(Xn).ptr());
CPyObject yp = bp::incref(bp::object(yn).ptr());
float result = pyWrap->score(id, Xp, yp);
return result;
}
void PyClassifier::setHyperparameters(const nlohmann::json& hyperparameters)
{
this->hyperparameters = hyperparameters;
}
} /* namespace pywrap */

61
pyclfs/PyClassifier.h Normal file
View File

@@ -0,0 +1,61 @@
#ifndef PYCLASSIFIER_H
#define PYCLASSIFIER_H
#include <string>
#include <map>
#include <vector>
#include <utility>
#include "boost/python/detail/wrap_python.hpp"
#include <boost/python/numpy.hpp>
#include <torch/torch.h>
#include <nlohmann/json.hpp>
#include "bayesnet/classifiers/Classifier.h"
#include "PyWrap.h"
#include "TypeId.h"
namespace pywrap {
class PyClassifier : public bayesnet::BaseClassifier {
public:
PyClassifier(const std::string& module, const std::string& className, const bool sklearn = false);
virtual ~PyClassifier();
PyClassifier& fit(std::vector<std::vector<int>>& X, std::vector<int>& y, const std::vector<std::string>& features, const std::string& className, std::map<std::string, std::vector<int>>& states) override { return *this; };
// X is nxm tensor, y is nx1 tensor
PyClassifier& fit(torch::Tensor& X, torch::Tensor& y, const std::vector<std::string>& features, const std::string& className, std::map<std::string, std::vector<int>>& states) override;
PyClassifier& fit(torch::Tensor& X, torch::Tensor& y);
PyClassifier& fit(torch::Tensor& dataset, const std::vector<std::string>& features, const std::string& className, std::map<std::string, std::vector<int>>& states) override { return *this; };
PyClassifier& fit(torch::Tensor& dataset, const std::vector<std::string>& features, const std::string& className, std::map<std::string, std::vector<int>>& states, const torch::Tensor& weights) override { return *this; };
torch::Tensor predict(torch::Tensor& X) override;
std::vector<int> predict(std::vector<std::vector<int >>& X) override { return std::vector<int>(); }; // Not implemented
torch::Tensor predict_proba(torch::Tensor& X) override { return torch::zeros({ 0, 0 }); } // Not implemented
std::vector<std::vector<double>> predict_proba(std::vector<std::vector<int >>& X) override { return std::vector<std::vector<double>>(); }; // Not implemented
float score(std::vector<std::vector<int>>& X, std::vector<int>& y) override { return 0.0; }; // Not implemented
float score(torch::Tensor& X, torch::Tensor& y) override;
int getClassNumStates() const override { return 0; };
std::string version();
std::string callMethodString(const std::string& method);
int callMethodSumOfItems(const std::string& method) const;
int callMethodInt(const std::string& method) const;
std::string getVersion() override { return this->version(); };
int getNumberOfNodes() const override { return 0; };
int getNumberOfEdges() const override { return 0; };
int getNumberOfStates() const override { return 0; };
std::vector<std::string> show() const override { return std::vector<std::string>(); }
std::vector<std::string> graph(const std::string& title = "") const override { return std::vector<std::string>(); }
bayesnet::status_t getStatus() const override { return bayesnet::NORMAL; };
std::vector<std::string> topological_order() override { return std::vector<std::string>(); }
void dump_cpt() const override {};
std::vector<std::string> getNotes() const override { return notes; };
void setHyperparameters(const nlohmann::json& hyperparameters) override;
protected:
nlohmann::json hyperparameters;
void trainModel(const torch::Tensor& weights) override {};
std::vector<std::string> notes;
private:
PyWrap* pyWrap;
std::string module;
std::string className;
bool sklearn;
clfId_t id;
bool fitted;
};
} /* namespace pywrap */
#endif /* PYCLASSIFIER_H */

87
pyclfs/PyHelper.hpp Normal file
View File

@@ -0,0 +1,87 @@
#ifndef PYHELPER_HPP
#define PYHELPER_HPP
#pragma once
// Code taken and adapted from
// https ://www.codeproject.com/Articles/820116/Embedding-Python-program-in-a-C-Cplusplus-code
#include "boost/python/detail/wrap_python.hpp"
#include <boost/python/numpy.hpp>
#include <iostream>
namespace pywrap {
namespace p = boost::python;
namespace np = boost::python::numpy;
class CPyInstance {
public:
CPyInstance()
{
Py_Initialize();
np::initialize();
}
~CPyInstance()
{
Py_Finalize();
}
};
class CPyObject {
private:
PyObject* p;
public:
CPyObject() : p(NULL)
{
}
CPyObject(PyObject* _p) : p(_p)
{
}
~CPyObject()
{
Release();
}
PyObject* getObject()
{
return p;
}
PyObject* setObject(PyObject* _p)
{
return (p = _p);
}
PyObject* AddRef()
{
if (p) {
Py_INCREF(p);
}
return p;
}
void Release()
{
if (p) {
Py_XDECREF(p);
}
p = NULL;
}
PyObject* operator ->()
{
return p;
}
bool is()
{
return p ? true : false;
}
operator PyObject* ()
{
return p;
}
PyObject* operator = (PyObject* pp)
{
p = pp;
return p;
}
operator bool()
{
return p ? true : false;
}
};
} /* namespace pywrap */
#endif

255
pyclfs/PyWrap.cc Normal file
View File

@@ -0,0 +1,255 @@
#define PY_SSIZE_T_CLEAN
#include <stdexcept>
#include "PyWrap.h"
#include <string>
#include <map>
#include <sstream>
#include <boost/python/numpy.hpp>
#include <iostream>
namespace pywrap {
namespace np = boost::python::numpy;
PyWrap* PyWrap::wrapper = nullptr;
std::mutex PyWrap::mutex;
CPyInstance* PyWrap::pyInstance = nullptr;
auto moduleClassMap = std::map<std::pair<std::string, std::string>, std::tuple<PyObject*, PyObject*, PyObject*>>();
PyWrap* PyWrap::GetInstance()
{
std::lock_guard<std::mutex> lock(mutex);
if (wrapper == nullptr) {
wrapper = new PyWrap();
pyInstance = new CPyInstance();
PyRun_SimpleString("import warnings;warnings.filterwarnings('ignore')");
}
return wrapper;
}
void PyWrap::RemoveInstance()
{
if (wrapper != nullptr) {
if (pyInstance != nullptr) {
delete pyInstance;
}
pyInstance = nullptr;
if (wrapper != nullptr) {
delete wrapper;
}
wrapper = nullptr;
}
}
void PyWrap::importClass(const clfId_t id, const std::string& moduleName, const std::string& className)
{
std::lock_guard<std::mutex> lock(mutex);
auto result = moduleClassMap.find(id);
if (result != moduleClassMap.end()) {
return;
}
PyObject* module = PyImport_ImportModule(moduleName.c_str());
if (PyErr_Occurred()) {
errorAbort("Couldn't import module " + moduleName);
}
PyObject* classObject = PyObject_GetAttrString(module, className.c_str());
if (PyErr_Occurred()) {
errorAbort("Couldn't find class " + className);
}
PyObject* instance = PyObject_CallObject(classObject, NULL);
if (PyErr_Occurred()) {
errorAbort("Couldn't create instance of class " + className);
}
moduleClassMap.insert({ id, { module, classObject, instance } });
}
void PyWrap::clean(const clfId_t id)
{
// Remove Python interpreter if no more modules imported left
std::lock_guard<std::mutex> lock(mutex);
auto result = moduleClassMap.find(id);
if (result == moduleClassMap.end()) {
return;
}
Py_DECREF(std::get<0>(result->second));
Py_DECREF(std::get<1>(result->second));
Py_DECREF(std::get<2>(result->second));
moduleClassMap.erase(result);
if (PyErr_Occurred()) {
PyErr_Print();
errorAbort("Error cleaning module ");
}
// With boost you can't remove the interpreter
// https://www.boost.org/doc/libs/1_83_0/libs/python/doc/html/tutorial/tutorial/embedding.html#tutorial.embedding.getting_started
// if (moduleClassMap.empty()) {
// RemoveInstance();
// }
}
void PyWrap::errorAbort(const std::string& message)
{
std::cerr << message << std::endl;
PyErr_Print();
RemoveInstance();
exit(1);
}
PyObject* PyWrap::getClass(const clfId_t id)
{
auto item = moduleClassMap.find(id);
if (item == moduleClassMap.end()) {
errorAbort("Module not found");
}
return std::get<2>(item->second);
}
std::string PyWrap::callMethodString(const clfId_t id, const std::string& method)
{
PyObject* instance = getClass(id);
PyObject* result;
try {
if (!(result = PyObject_CallMethod(instance, method.c_str(), NULL)))
errorAbort("Couldn't call method " + method);
}
catch (const std::exception& e) {
errorAbort(e.what());
}
std::string value = PyUnicode_AsUTF8(result);
Py_XDECREF(result);
return value;
}
int PyWrap::callMethodInt(const clfId_t id, const std::string& method)
{
PyObject* instance = getClass(id);
PyObject* result;
try {
if (!(result = PyObject_CallMethod(instance, method.c_str(), NULL)))
errorAbort("Couldn't call method " + method);
}
catch (const std::exception& e) {
errorAbort(e.what());
}
int value = PyLong_AsLong(result);
Py_XDECREF(result);
return value;
}
std::string PyWrap::sklearnVersion()
{
PyObject* sklearnModule = PyImport_ImportModule("sklearn");
if (sklearnModule == nullptr) {
errorAbort("Couldn't import sklearn");
}
PyObject* versionAttr = PyObject_GetAttrString(sklearnModule, "__version__");
if (versionAttr == nullptr || !PyUnicode_Check(versionAttr)) {
Py_XDECREF(sklearnModule);
errorAbort("Couldn't get sklearn version");
}
std::string result = PyUnicode_AsUTF8(versionAttr);
Py_XDECREF(versionAttr);
Py_XDECREF(sklearnModule);
return result;
}
std::string PyWrap::version(const clfId_t id)
{
return callMethodString(id, "version");
}
int PyWrap::callMethodSumOfItems(const clfId_t id, const std::string& method)
{
// Call method on each estimator and sum the results (made for RandomForest)
PyObject* instance = getClass(id);
PyObject* estimators = PyObject_GetAttrString(instance, "estimators_");
if (estimators == nullptr) {
errorAbort("Failed to get attribute: " + method);
}
int sumOfItems = 0;
Py_ssize_t len = PyList_Size(estimators);
for (Py_ssize_t i = 0; i < len; i++) {
PyObject* estimator = PyList_GetItem(estimators, i);
PyObject* result;
if (method == "node_count") {
PyObject* owner = PyObject_GetAttrString(estimator, "tree_");
if (owner == nullptr) {
Py_XDECREF(estimators);
errorAbort("Failed to get attribute tree_ for: " + method);
}
result = PyObject_GetAttrString(owner, method.c_str());
if (result == nullptr) {
Py_XDECREF(estimators);
Py_XDECREF(owner);
errorAbort("Failed to get attribute node_count: " + method);
}
Py_DECREF(owner);
} else {
result = PyObject_CallMethod(estimator, method.c_str(), nullptr);
if (result == nullptr) {
Py_XDECREF(estimators);
errorAbort("Failed to call method: " + method);
}
}
sumOfItems += PyLong_AsLong(result);
Py_DECREF(result);
}
Py_DECREF(estimators);
return sumOfItems;
}
void PyWrap::setHyperparameters(const clfId_t id, const json& hyperparameters)
{
// Set hyperparameters as attributes of the class
PyObject* pValue;
PyObject* instance = getClass(id);
for (const auto& [key, value] : hyperparameters.items()) {
std::stringstream oss;
oss << value.type_name();
if (oss.str() == "string") {
pValue = Py_BuildValue("s", value.get<std::string>().c_str());
} else {
if (value.is_number_integer()) {
pValue = Py_BuildValue("i", value.get<int>());
} else {
pValue = Py_BuildValue("f", value.get<double>());
}
}
int res = PyObject_SetAttrString(instance, key.c_str(), pValue);
if (res == -1 && PyErr_Occurred()) {
Py_XDECREF(pValue);
errorAbort("Couldn't set attribute " + key + "=" + value.dump());
}
Py_XDECREF(pValue);
}
}
void PyWrap::fit(const clfId_t id, CPyObject& X, CPyObject& y)
{
PyObject* instance = getClass(id);
CPyObject result;
CPyObject method = PyUnicode_FromString("fit");
try {
if (!(result = PyObject_CallMethodObjArgs(instance, method.getObject(), X.getObject(), y.getObject(), NULL)))
errorAbort("Couldn't call method fit");
}
catch (const std::exception& e) {
errorAbort(e.what());
}
}
PyObject* PyWrap::predict(const clfId_t id, CPyObject& X)
{
PyObject* instance = getClass(id);
PyObject* result;
CPyObject method = PyUnicode_FromString("predict");
try {
if (!(result = PyObject_CallMethodObjArgs(instance, method.getObject(), X.getObject(), NULL)))
errorAbort("Couldn't call method predict");
}
catch (const std::exception& e) {
errorAbort(e.what());
}
Py_INCREF(result);
return result; // Caller must free this object
}
double PyWrap::score(const clfId_t id, CPyObject& X, CPyObject& y)
{
PyObject* instance = getClass(id);
CPyObject result;
CPyObject method = PyUnicode_FromString("score");
try {
if (!(result = PyObject_CallMethodObjArgs(instance, method.getObject(), X.getObject(), y.getObject(), NULL)))
errorAbort("Couldn't call method score");
}
catch (const std::exception& e) {
errorAbort(e.what());
}
double resultValue = PyFloat_AsDouble(result);
return resultValue;
}
}

49
pyclfs/PyWrap.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef PYWRAP_H
#define PYWRAP_H
#include <string>
#include <map>
#include <tuple>
#include <mutex>
#include <nlohmann/json.hpp>
#include "boost/python/detail/wrap_python.hpp"
#include "PyHelper.hpp"
#include "TypeId.h"
#pragma once
namespace pywrap {
/*
Singleton class to handle Python/numpy interpreter.
*/
using json = nlohmann::json;
class PyWrap {
public:
PyWrap() = default;
PyWrap(PyWrap& other) = delete;
static PyWrap* GetInstance();
void operator=(const PyWrap&) = delete;
~PyWrap() = default;
std::string callMethodString(const clfId_t id, const std::string& method);
int callMethodInt(const clfId_t id, const std::string& method);
std::string sklearnVersion();
std::string version(const clfId_t id);
int callMethodSumOfItems(const clfId_t id, const std::string& method);
void setHyperparameters(const clfId_t id, const json& hyperparameters);
void fit(const clfId_t id, CPyObject& X, CPyObject& y);
PyObject* predict(const clfId_t id, CPyObject& X);
double score(const clfId_t id, CPyObject& X, CPyObject& y);
void clean(const clfId_t id);
void importClass(const clfId_t id, const std::string& moduleName, const std::string& className);
PyObject* getClass(const clfId_t id);
private:
// Only call RemoveInstance from clean method
static void RemoveInstance();
void errorAbort(const std::string& message);
// No need to use static map here, since this class is a singleton
std::map<clfId_t, std::tuple<PyObject*, PyObject*, PyObject*>> moduleClassMap;
static CPyInstance* pyInstance;
static PyWrap* wrapper;
static std::mutex mutex;
};
} /* namespace pywrap */
#endif /* PYWRAP_H */

20
pyclfs/RandomForest.cc Normal file
View File

@@ -0,0 +1,20 @@
#include "RandomForest.h"
namespace pywrap {
RandomForest::RandomForest() : PyClassifier("sklearn.ensemble", "RandomForestClassifier", true)
{
validHyperparameters = { "n_estimators", "n_jobs", "random_state" };
}
int RandomForest::getNumberOfEdges() const
{
return callMethodSumOfItems("get_n_leaves");
}
int RandomForest::getNumberOfStates() const
{
return callMethodSumOfItems("get_depth");
}
int RandomForest::getNumberOfNodes() const
{
return callMethodSumOfItems("node_count");
}
} /* namespace pywrap */

15
pyclfs/RandomForest.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef RANDOMFOREST_H
#define RANDOMFOREST_H
#include "PyClassifier.h"
namespace pywrap {
class RandomForest : public PyClassifier {
public:
RandomForest();
~RandomForest() = default;
int getNumberOfEdges() const override;
int getNumberOfStates() const override;
int getNumberOfNodes() const override;
};
} /* namespace pywrap */
#endif /* RANDOMFOREST_H */

24
pyclfs/STree.cc Normal file
View File

@@ -0,0 +1,24 @@
#include "STree.h"
namespace pywrap {
STree::STree() : PyClassifier("stree", "Stree")
{
validHyperparameters = { "C", "kernel", "max_iter", "max_depth", "random_state", "multiclass_strategy", "gamma", "max_features", "degree" };
};
int STree::getNumberOfNodes() const
{
return callMethodInt("get_nodes");
}
int STree::getNumberOfEdges() const
{
return callMethodInt("get_leaves");
}
int STree::getNumberOfStates() const
{
return callMethodInt("get_depth");
}
std::string STree::graph()
{
return callMethodString("graph");
}
} /* namespace pywrap */

17
pyclfs/STree.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef STREE_H
#define STREE_H
#include "nlohmann/json.hpp"
#include "PyClassifier.h"
namespace pywrap {
class STree : public PyClassifier {
public:
STree();
~STree() = default;
int getNumberOfNodes() const override;
int getNumberOfEdges() const override;
int getNumberOfStates() const override;
std::string graph();
};
} /* namespace pywrap */
#endif /* STREE_H */

8
pyclfs/SVC.cc Normal file
View File

@@ -0,0 +1,8 @@
#include "SVC.h"
namespace pywrap {
SVC::SVC() : PyClassifier("sklearn.svm", "SVC", true)
{
validHyperparameters = { "C", "gamma", "kernel", "random_state" };
}
} /* namespace pywrap */

13
pyclfs/SVC.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef SVC_H
#define SVC_H
#include "PyClassifier.h"
namespace pywrap {
class SVC : public PyClassifier {
public:
SVC();
~SVC() = default;
};
} /* namespace pywrap */
#endif /* SVC_H */

6
pyclfs/TypeId.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef TYPEDEF_H
#define TYPEDEF_H
namespace pywrap {
typedef uint64_t clfId_t;
}
#endif /* TYPEDEF_H */

9
pyclfs/XGBoost.cc Normal file
View File

@@ -0,0 +1,9 @@
#include "XGBoost.h"
//See https ://stackoverflow.com/questions/36071672/using-xgboost-in-c
namespace pywrap {
XGBoost::XGBoost() : PyClassifier("xgboost", "XGBClassifier", true)
{
validHyperparameters = { "tree_method", "early_stopping_rounds", "n_jobs" };
}
} /* namespace pywrap */

12
pyclfs/XGBoost.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef XGBOOST_H
#define XGBOOST_H
#include "PyClassifier.h"
namespace pywrap {
class XGBoost : public PyClassifier {
public:
XGBoost();
~XGBoost() = default;
};
} /* namespace pywrap */
#endif /* XGBOOST_H */