Commit a7987fd5 authored by Thomas Purcell's avatar Thomas Purcell
Browse files

Making python bindings optional

This will make it easier for users who do not want to use the bindings
parent df003950
......@@ -16,64 +16,77 @@ set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Check python settings
find_package(PythonInterp 3 REQUIRED)
execute_process(COMMAND ${PYTHON_EXECUTABLE}
option(USE_PYTHON "Whether to compile with python binding support" OFF)
if(USE_PYTHON)
message(STATUS "USE PYTHON True")
set(USE_PYTHON TRUE)
else(USE_PYTHON)
message(STATUS "USE PYTHON False")
set(USE_PYTHON FALSE)
endif()
if(USE_PYTHON)
# Check python settings
find_package(PythonInterp 3 REQUIRED)
execute_process(COMMAND ${PYTHON_EXECUTABLE}
-c "import distutils.sysconfig as cg; print(cg.get_python_inc())"
OUTPUT_VARIABLE PYTHON_INCLUDE_PATH
OUTPUT_STRIP_TRAILING_WHITESPACE)
set(PYTHON_INCLUDE_PATH ${PYTHON_INCLUDE_PATH} CACHE PATH "Python Include Directory")
mark_as_advanced(PYTHON_INCLUDE_PATH)
set(PYTHON_INCLUDE_PATH ${PYTHON_INCLUDE_PATH} CACHE PATH "Python Include Directory")
mark_as_advanced(PYTHON_INCLUDE_PATH)
include_directories(${PYTHON_INCLUDE_PATH})
include_directories(${PYTHON_INCLUDE_PATH})
if(NOT PYTHON_INSTDIR)
execute_process(COMMAND ${PYTHON_EXECUTABLE}
if(NOT PYTHON_INSTDIR)
execute_process(COMMAND ${PYTHON_EXECUTABLE}
-c "import distutils.sysconfig as cg; print(cg.get_python_lib(1,0))"
OUTPUT_VARIABLE PYTHON_INSTDIR OUTPUT_STRIP_TRAILING_WHITESPACE)
endif()
endif()
execute_process(COMMAND ${PYTHON_EXECUTABLE}
execute_process(COMMAND ${PYTHON_EXECUTABLE}
-c "import sys; print('{}{}'.format(sys.version_info[0], sys.version_info[1]))"
OUTPUT_VARIABLE BOOST_PYTHON_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${PYTHON_EXECUTABLE}
execute_process(COMMAND ${PYTHON_EXECUTABLE}
-c "import sys; print(sys.version[:3])"
OUTPUT_VARIABLE PYTHON_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${PYTHON_EXECUTABLE}
execute_process(COMMAND ${PYTHON_EXECUTABLE}
-c "import distutils.sysconfig as cg; print(cg.get_config_var('LIBDIR'))"
OUTPUT_VARIABLE PYTHON_LIBDIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
message(STATUS "PYTHON_LIBDIR = ${PYTHON_LIBDIR}")
message(STATUS "PYTHON_INSTDIR = ${PYTHON_INSTDIR}")
message(STATUS "PYTHON_LIBDIR = ${PYTHON_LIBDIR}")
message(STATUS "PYTHON_INSTDIR = ${PYTHON_INSTDIR}")
find_library(PYTHON_LIBRARIES
find_library(PYTHON_LIBRARIES
NAMES python${PYTHON_VERSION}m
PATHS ${PYTHON_LIBDIR} )
if(NOT PYTHON_LIBRARIES)
if(NOT PYTHON_LIBRARIES)
message(FATAL_ERROR "Python libraries not found!")
endif(NOT PYTHON_LIBRARIES)
message(STATUS "PYTHON_LIBRARIES = ${PYTHON_LIBRARIES}")
endif(NOT PYTHON_LIBRARIES)
message(STATUS "PYTHON_LIBRARIES = ${PYTHON_LIBRARIES}")
mark_as_advanced(PYTHON_INCLUDE_PATH PYTHON_LIBRARIES)
mark_as_advanced(PYTHON_INCLUDE_PATH PYTHON_LIBRARIES)
endif()
# Check for Boost
find_package(Boost REQUIRED COMPONENTS filesystem system mpi serialization)
set(Boost_LIBS ${Boost_LIBRARIES})
if(${Boost_VERSION} VERSION_LESS 106700)
if(USE_PYTHON)
if(${Boost_VERSION} VERSION_LESS 106700)
find_package(Boost ${NEEDED_Boost_VERSION} REQUIRED COMPONENTS python numpy)
else()
else()
find_package(Boost ${NEEDED_Boost_VERSION} REQUIRED COMPONENTS python${BOOST_PYTHON_VERSION} numpy${BOOST_PYTHON_VERSION})
endif()
endif()
# Append Python library to the list of Boost libraries.
list(APPEND Boost_LIBS ${Boost_LIBRARIES})
set(Boost_LIBRARIES ${Boost_LIBS})
......
file(GLOB_RECURSE SISSOPP_SOURCES *.cpp)
file(GLOB_RECURSE SISSOLIB_SOURCES *.cpp)
file(GLOB_RECURSE NOT_SISSOPP_SOURCES python/*.cpp)
list(REMOVE_ITEM SISSOPP_SOURCES ${NOT_SISSOPP_SOURCES})
list(REMOVE_ITEM SISSOLIB_SOURCES main.cpp)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sisso++_config.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/sisso++_config.hpp)
add_executable(sisso++ ${SISSOPP_SOURCES})
add_library(_sisso SHARED ${SISSOLIB_SOURCES})
include_directories(${CMAKE_CURRENT_LIST_DIR}/descriptor_identifier/)
include_directories(${CMAKE_CURRENT_LIST_DIR}/descriptor_identifier/Model)
include_directories(${CMAKE_CURRENT_LIST_DIR}/feature_creation/node/)
......@@ -27,6 +12,14 @@ include_directories(${CMAKE_CURRENT_LIST_DIR}/mpi_interface/)
include_directories(${CMAKE_CURRENT_LIST_DIR}/python/)
include_directories(${CMAKE_CURRENT_LIST_DIR}/utils/)
file(GLOB_RECURSE SISSOPP_SOURCES *.cpp)
file(GLOB_RECURSE NOT_SISSOPP_SOURCES python/*.cpp)
list(REMOVE_ITEM SISSOPP_SOURCES ${NOT_SISSOPP_SOURCES})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sisso++_config.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/sisso++_config.hpp)
add_executable(sisso++ ${SISSOPP_SOURCES})
set_target_properties(sisso++
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
......@@ -34,22 +27,29 @@ set_target_properties(sisso++
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
target_link_libraries(sisso++ ${LAPACK_LIBRARIES} ${MPI_LIBRARIES} ${PYTHON_LIBRARIES} ${Boost_LIBS})
install(TARGETS sisso++ DESTINATION ${CMAKE_CURRENT_LIST_DIR}/../bin/)
set_target_properties(_sisso
if(USE_PYTHON)
file(GLOB_RECURSE SISSOLIB_SOURCES *.cpp)
list(REMOVE_ITEM SISSOLIB_SOURCES main.cpp)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPY_BINDINGS")
add_library(_sisso SHARED ${SISSOLIB_SOURCES})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/python/__init__.py ${CMAKE_CURRENT_LIST_DIR}/python/__init__.py COPYONLY)
set_target_properties(_sisso
PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
PREFIX ""
SUFFIX ".so"
)
target_link_libraries(_sisso ${LAPACK_LIBRARIES} ${MPI_LIBRARIES} ${PYTHON_LIBRARIES} ${Boost_LIBS})
)
target_link_libraries(_sisso ${LAPACK_LIBRARIES} ${MPI_LIBRARIES} ${PYTHON_LIBRARIES} ${Boost_LIBS})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/python/__init__.py ${CMAKE_CURRENT_LIST_DIR}/python/__init__.py COPYONLY)
install(TARGETS _sisso DESTINATION "${PYTHON_INSTDIR}/sisso")
install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/python/ DESTINATION ${PYTHON_INSTDIR}/sisso
install(TARGETS _sisso DESTINATION "${PYTHON_INSTDIR}/sisso")
install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/python/ DESTINATION ${PYTHON_INSTDIR}/sisso
FILES_MATCHING PATTERN "*.py"
PATTERN "CMakeFiles" EXCLUDE)
install(TARGETS sisso++ DESTINATION ${CMAKE_CURRENT_LIST_DIR}/../bin/)
\ No newline at end of file
endif()
......@@ -331,30 +331,3 @@ void Model::to_file(std::string filename, bool train, std::vector<int> test_inds
}
out_file_stream.close();
}
void Model::register_python()
{
using namespace boost::python;
class_<Model>("Model", init<std::vector<double>, std::vector<double>, std::vector<model_node_ptr>, std::vector<int>, std::vector<int>>())
.def(init<std::string>())
.def(init<std::string, std::string>())
.def("predict", &Model::predict)
.def("fit", &Model::predict_train)
.def("__str__", &Model::toString)
.def("__repr__", &Model::toString)
.def_readonly("_n_samp_train", &Model::_n_samp_train)
.def_readonly("_n_samp_test", &Model::_n_samp_test)
.def_readonly("_n_dim", &Model::_n_dim)
.add_property("prop_train_est", &Model::prop_train_est)
.add_property("prop_test_est", &Model::prop_test_est)
.add_property("prop_train", &Model::prop_train)
.add_property("prop_test", &Model::prop_test)
.add_property("train_error", &Model::train_error)
.add_property("test_error", &Model::test_error)
.add_property("feats", &Model::feats)
.add_property("coefs", &Model::coefs)
.add_property("rmse", &Model::rmse)
.add_property("test_rmse", &Model::test_rmse)
.add_property("max_ae", &Model::max_ae)
.add_property("test_max_ae", &Model::test_max_ae);
}
......@@ -4,7 +4,6 @@
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/filesystem.hpp>
#include <boost/python.hpp>
#include<iomanip>
#include<fstream>
......@@ -13,8 +12,10 @@
#include <feature_creation/node/ModelNode.hpp>
#include <utils/string_utils.hpp>
namespace python = boost::python;
namespace np = boost::python::numpy;
#ifdef PY_BINDINGS
namespace np = boost::python::numpy;
namespace py = boost::python;
#endif
typedef std::shared_ptr<ModelNode> model_node_ptr;
/**
......@@ -85,6 +86,9 @@ public:
*/
inline double rmse(){return util_funcs::norm(_train_error.data(), _n_samp_train) / std::sqrt(static_cast<double>(_n_samp_train));}
inline double test_rmse(){return util_funcs::norm(_test_error.data(), _n_samp_test) / std::sqrt(static_cast<double>(_n_samp_test));}
inline int n_samp_train(){return _n_samp_train;}
inline int n_samp_test(){return _n_samp_test;}
inline int n_dim(){return _n_dim;}
/**
* @brief The max Absolute error of the array
......@@ -99,17 +103,23 @@ public:
return std::abs(*std::max_element(_test_error.data(), _test_error.data() + _n_samp_test, [](double d1, double d2){return std::abs(d1) < std::abs(d2);}));
}
inline python::list coefs()
/**
* @brief Print model to a file
*/
void to_file(std::string filename, bool train = true, std::vector<int> test_inds = {});
#ifdef PY_BINDINGS
inline py::list coefs()
{
python::list coef_lst;
py::list coef_lst;
for(auto& task_coefs : _coefs)
coef_lst.append<python::list>(python_conv_utils::to_list<double>(task_coefs));
coef_lst.append<py::list>(python_conv_utils::to_list<double>(task_coefs));
return coef_lst;
}
inline python::list feats()
inline py::list feats()
{
python::list feat_lst;
py::list feat_lst;
for(auto& feat : _feats)
feat_lst.append<ModelNode>(*feat);
return feat_lst;
......@@ -121,14 +131,7 @@ public:
inline np::ndarray prop_test(){return python_conv_utils::to_ndarray<double>(_prop_test);}
inline np::ndarray train_error(){return python_conv_utils::to_ndarray<double>(_train_error);}
inline np::ndarray test_error(){return python_conv_utils::to_ndarray<double>(_test_error);}
/**
* @brief Print model to a file
*/
void to_file(std::string filename, bool train = true, std::vector<int> test_inds = {});
static void register_python();
#endif
};
/**
......
#include <descriptor_identifier/SISSORegressor.hpp>
SISSORegressor::SISSORegressor(
std::shared_ptr<FeatureSpace> feat_space,
std::vector<double> prop,
......@@ -39,82 +38,6 @@ SISSORegressor::SISSORegressor(
_work = std::vector<double>(_lwork, 0.0);
}
SISSORegressor::SISSORegressor(
std::shared_ptr<FeatureSpace> feat_space,
np::ndarray prop,
np::ndarray prop_test,
python::list task_sizes_train,
python::list task_sizes_test,
python::list leave_out_inds,
int n_dim,
int n_residual
) :
_prop(python_conv_utils::from_ndarray<double>(prop)),
_prop_test(python_conv_utils::from_ndarray<double>(prop_test)),
_a((n_dim + 1) * prop.shape(0)),
_b(prop.shape(0)),
_error(prop.shape(0), 0.0),
_s(n_dim + 1),
_task_sizes_train(python_conv_utils::from_list<int>(task_sizes_train)),
_task_sizes_test(python_conv_utils::from_list<int>(task_sizes_test)),
_leave_out_inds(python_conv_utils::from_list<int>(leave_out_inds)),
_feat_space(feat_space),
_mpi_comm(feat_space->mpi_comm()),
_n_samp(prop.shape(0)),
_n_dim(n_dim),
_n_residual(n_residual),
_lwork(-1),
_rank(0)
{
node_value_arrs::initialize_d_matrix_arr();
// Initialize a, b, ones, s, and _error arrays
std::fill_n(_a.data(), (_n_dim + 1) * _n_samp, 0.0);
std::fill_n(_b.data(), _n_samp, 0.0);
std::fill_n(_s.data(), _n_dim + 1, 0.0);
// // Get optimal work size
_lwork = get_opt_lwork(_n_dim + 1);
_work = std::vector<double>(_lwork, 0.0);
}
SISSORegressor::SISSORegressor(
std::shared_ptr<FeatureSpace> feat_space,
python::list prop,
python::list prop_test,
python::list task_sizes_train,
python::list task_sizes_test,
python::list leave_out_inds,
int n_dim,
int n_residual
) :
_prop(python_conv_utils::from_list<double>(prop)),
_prop_test(python_conv_utils::from_list<double>(prop_test)),
_a((n_dim + 1) * boost::python::len(prop)),
_b(boost::python::len(prop)),
_error(boost::python::len(prop), 0.0),
_s(n_dim + 1),
_task_sizes_train(python_conv_utils::from_list<int>(task_sizes_train)),
_task_sizes_test(python_conv_utils::from_list<int>(task_sizes_test)),
_leave_out_inds(python_conv_utils::from_list<int>(leave_out_inds)),
_feat_space(feat_space),
_mpi_comm(feat_space->mpi_comm()),
_n_samp(boost::python::len(prop)),
_n_dim(n_dim),
_n_residual(n_residual),
_lwork(-1),
_rank(0)
{
node_value_arrs::initialize_d_matrix_arr();
// Initialize a, b, ones, s, and _error arrays
std::fill_n(_a.data(), (_n_dim + 1) * _n_samp, 0.0);
std::fill_n(_b.data(), _n_samp, 0.0);
std::fill_n(_s.data(), _n_dim + 1, 0.0);
// // Get optimal work size
_lwork = get_opt_lwork(_n_dim + 1);
_work = std::vector<double>(_lwork, 0.0);
}
void SISSORegressor::set_a(std::vector<int>& inds, int start, int n_samp)
{
for(int ii = 0; ii < inds.size(); ++ii)
......@@ -305,31 +228,3 @@ void SISSORegressor::l0_norm(std::vector<double>& prop, int n_dim)
_models.push_back(models);
}
python::list SISSORegressor::models_py()
{
python::list model_list;
for(auto& m_list : _models)
model_list.append<python::list>(python_conv_utils::to_list<Model>(m_list));
return model_list;
}
void SISSORegressor::register_python()
{
using namespace boost::python;
class_<SISSORegressor>("SISSORegressor", init<std::shared_ptr<FeatureSpace>, np::ndarray, np::ndarray, python::list, python::list, python::list, int, int>())
.def(init<std::shared_ptr<FeatureSpace>, python::list, python::list, python::list, python::list, python::list, int, int>())
.def("fit", &SISSORegressor::fit)
.add_property("prop", &SISSORegressor::prop_py)
.add_property("prop_test", &SISSORegressor::prop_test_py)
.add_property("models", &SISSORegressor::models_py)
.add_property("n_samp", &SISSORegressor::n_samp)
.add_property("n_dim", &SISSORegressor::n_dim)
.add_property("n_residual", &SISSORegressor::n_residual)
.add_property("feat_space", &SISSORegressor::feat_space)
.add_property("error", &SISSORegressor::error_py)
.add_property("task_sizes_train", &SISSORegressor::task_sizes_train)
.add_property("task_sizes_test", &SISSORegressor::task_sizes_test)
;
}
\ No newline at end of file
......@@ -5,8 +5,11 @@
#include <descriptor_identifier/Model/Model.hpp>
#include <ctime>
namespace python = boost::python;
namespace np = boost::python::numpy;
#ifdef PY_BINDINGS
namespace np = boost::python::numpy;
namespace py = boost::python;
#endif
/**
* @brief SISSO Regressor class, to find the best models, and store them
*
......@@ -56,28 +59,6 @@ public:
int n_dim,
int n_residual);
SISSORegressor(
std::shared_ptr<FeatureSpace> feat_space,
np::ndarray prop,
np::ndarray prop_test,
python::list task_sizes_train,
python::list task_sizes_test,
python::list leave_out_inds,
int n_dim,
int n_residual
);
SISSORegressor(
std::shared_ptr<FeatureSpace> feat_space,
python::list prop,
python::list prop_test,
python::list task_sizes_train,
python::list task_sizes_test,
python::list leave_out_inds,
int n_dim,
int n_residual
);
/**
* @brief Get the optimal size of the working array
*
......@@ -132,22 +113,21 @@ public:
*/
inline std::vector<double> prop(){return _prop;}
inline np::ndarray prop_py(){return python_conv_utils::to_ndarray<double>(_prop);}
/**
* @brief Acessor function for prop
*/
inline std::vector<double> prop_test(){return _prop_test;}
inline np::ndarray prop_test_py(){return python_conv_utils::to_ndarray<double>(_prop_test);}
/**
* @brief Acessor function for {
*/
inline std::vector<double> error(){return _error;}
/**
* @brief Acessor function for models
*/
inline std::vector<std::vector<Model>> models(){return _models;}
python::list models_py();
/**
* @brief Acessor function for n_samp
*/
......@@ -163,17 +143,37 @@ public:
*/
inline int n_residual(){return _n_residual;}
inline python::list task_sizes_train(){python_conv_utils::to_list<int>(_task_sizes_train);}
inline python::list task_sizes_test(){python_conv_utils::to_list<int>(_task_sizes_test);}
// Python interface functions
#ifdef PY_BINDINGS
SISSORegressor(
std::shared_ptr<FeatureSpace> feat_space,
np::ndarray prop,
np::ndarray prop_test,
py::list task_sizes_train,
py::list task_sizes_test,
py::list leave_out_inds,
int n_dim,
int n_residual
);
/**
* @brief Acessor function for {
*/
inline std::vector<double> error(){return _error;}
SISSORegressor(
std::shared_ptr<FeatureSpace> feat_space,
py::list prop,
py::list prop_test,
py::list task_sizes_train,
py::list task_sizes_test,
py::list leave_out_inds,
int n_dim,
int n_residual
);
py::list models_py();
inline np::ndarray prop_py(){return python_conv_utils::to_ndarray<double>(_prop);}
inline np::ndarray prop_test_py(){return python_conv_utils::to_ndarray<double>(_prop_test);}
inline py::list task_sizes_train(){python_conv_utils::to_list<int>(_task_sizes_train);}
inline py::list task_sizes_test(){python_conv_utils::to_list<int>(_task_sizes_test);}
inline np::ndarray error_py(){return python_conv_utils::to_ndarray<double>(_error);}
static void register_python();
#endif
};
#endif
#include <feature_creation/feature_space/FeatureSpace.hpp>
BOOST_CLASS_EXPORT_GUID(FeatureNode, "FeatureNode")
BOOST_CLASS_EXPORT_GUID(AddNode, "AddNode")
BOOST_CLASS_EXPORT_GUID(SubNode, "SubNode")
......@@ -52,86 +53,6 @@ FeatureSpace::FeatureSpace(
initialize_fs(prop);
}
FeatureSpace::FeatureSpace(
python::list phi_0,
python::list allowed_ops,
python::list prop,
python::list task_sizes,
int max_phi,
int n_sis_select,
int max_store_rung,
int n_rung_generate,
double min_abs_feat_val,
double max_abs_feat_val
):
_phi(python_conv_utils::shared_ptr_vec_from_list<Node, FeatureNode>(phi_0)),
_phi_0(_phi),
_allowed_ops(python_conv_utils::from_list<std::string>(allowed_ops)),
_scores(python::len(phi_0), 0.0),
_task_sizes(python_conv_utils::from_list<int>(task_sizes)),
_start_gen(1, 0),
_feature_space_file("feature_space/selected_features.txt"),
_mpi_comm(mpi_setup::comm),
_l_bound(min_abs_feat_val),
_u_bound(max_abs_feat_val),
_max_phi(max_phi),
_n_sis_select(n_sis_select),
_n_feat(python::len(phi_0)),
_n_rung_store(max_store_rung),
_n_rung_generate(n_rung_generate)
{
_n_samp = _phi_0[0]->n_samp();
initialize_fs(python_conv_utils::from_list<double>(prop));
}
FeatureSpace::FeatureSpace(
python::list phi_0,
python::list allowed_ops,
np::ndarray prop,
python::list task_sizes,
int max_phi,
int n_sis_select,
int max_store_rung,
int n_rung_generate,
double min_abs_feat_val,
double max_abs_feat_val
):
_phi(python_conv_utils::shared_ptr_vec_from_list<Node, FeatureNode>(phi_0)),
_phi_0(_phi),
_allowed_ops(python_conv_utils::from_list<std::string>(allowed_ops)),
_scores(python::len(phi_0), 0.0),
_task_sizes(python_conv_utils::from_list<int>(task_sizes)),
_start_gen(1, 0),
_feature_space_file("feature_space/selected_features.txt"),
_mpi_comm(mpi_setup::comm),
_l_bound(min_abs_feat_val),
_u_bound(max_abs_feat_val),
_max_phi(max_phi),
_n_sis_select(n_sis_select),
_n_feat(python::len(phi_0)),
_n_rung_store(max_store_rung),
_n_rung_generate(n_rung_generate)
{
_n_samp = _phi_0[0]->n_samp();
initialize_fs(python_conv_utils::from_ndarray<double>(prop));
}
boost::python::list FeatureSpace::phi0_py()
{
python::list feat_lst;
for(auto& feat : _phi_0)
feat_lst.append<FeatureNode>(FeatureNode(feat->feat_ind(), feat->expr(), feat->value(), feat->test_value(), feat->unit()));
return feat_lst;
}
boost::python::list FeatureSpace::phi_selected_py()
{
python::list feat_lst;