Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ jobs:
env:
SKIP: no-commit-to-branch
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-python@v3
- uses: pre-commit/action@v3.0.0
2 changes: 1 addition & 1 deletion .github/workflows/run-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
submodule: recursive
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
Expand All @@ -17,7 +18,6 @@ jobs:
run: |
pip install --upgrade pip setuptools wheel
sudo apt-get install lcov
pip install pybind11 numpy
FLAGS="-fprofile-arcs -ftest-coverage"
export CFLAGS="$FLAGS"
export CXXFLAGS="$FLAGS"
Expand Down
57 changes: 26 additions & 31 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,19 @@ jobs:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/checkout@v4
- uses: actions/setup-python@v3
name: Install Python
with:
python-version: '3.13'
- name: Build sdist
run: pip install pybind11 setuptools_scm && python setup.py sdist
run: pip install build && python -m build --sdist
- uses: actions/upload-artifact@v4
with:
path: dist/*.tar.gz
build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
env:
CIBW_ENVIRONMENT: "${{ matrix.cibw.env || '' }}"
runs-on: ${{ matrix.runs-on }}
strategy:
matrix:
include:
Expand All @@ -41,56 +37,55 @@ jobs:
runs-on: ubuntu-24.04-arm
cibw:
cflags: ''
# - os: windows-intel
# runs-on: windows-latest
# cibw:
# cflags: ''
# - os: windows-arm
# runs-on: windows-11-arm
# cibw:
# cflags: ''
# - os: macos-intel
# runs-on: macos-13
# cibw:
# cflags: ''
# - os: macos-arm
# runs-on: macos-latest
# cibw:
# cflags: ''
- os: windows-intel
runs-on: windows-latest
cibw:
cflags: ''
- os: macos-intel
runs-on: macos-13
cibw:
cflags: ''
- os: macos-arm
runs-on: macos-latest
cibw:
cflags: ''
steps:
- uses: actions/checkout@v4
with:
submodule: recursive
fetch-depth: 0
- name: Build wheels
uses: pypa/cibuildwheel@v3.0.1
env:
CIBW_PLATFORM: ${{ matrix.platform || 'auto' }}
CFLAGS: ${{ matrix.cibw.cflags }}
CXXFLAGS: ${{ matrix.cibw.cflags }}
- uses: actions/upload-artifact@v4
with:
name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }}
path: ./wheelhouse/*.whl
upload_pypi:
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v2
- uses: actions/download-artifact@v4
with:
name: artifact
pattern: cibw-*
path: dist
merge-multiple: true
- name: Publish package to TestPyPI
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.TEST_PYPI_APITOKEN }}
packages_dir: dist/
repository_url: https://test.pypi.org/legacy/
verbose: true
skip_existing: true
- name: Publish package to PyPI
if: github.event_name == 'release'
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_APITOKEN }}
packages_dir: dist/
verbose: true
skip_existing: true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ docs/build/
docs/source/api_reference/
.cache/
.coverage
src/irspack/_version.py
Empty file added .gitmodules
Empty file.
50 changes: 38 additions & 12 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
cmake_minimum_required(VERSION 3.0.0)
project(rs_evaluation VERSION 0.1.0)
set(CMAKE_BUILD_TYPE DEBUG)
set(CMAKE_CXX_FLAGS "-std=c++11 -march=native -fPIC -O0 -g")
cmake_minimum_required(VERSION 3.15...3.27)
project(irspack VERSION 0.1.0)

if (CMAKE_VERSION VERSION_LESS 3.18)
set(DEV_MODULE Development)
else()
set(DEV_MODULE Development.Module)
endif()


if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()

if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/eigen-3.4.0")
file(DOWNLOAD https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.zip "${CMAKE_CURRENT_BINARY_DIR}/eigen-3.4.0.zip")
file(ARCHIVE_EXTRACT INPUT "${CMAKE_CURRENT_BINARY_DIR}/eigen-3.4.0.zip")
endif()

find_package(Threads REQUIRED)

include_directories(eigen-3.3.7 cpp_source)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

include_directories("${CMAKE_BINARY_DIR}/eigen-3.4.0" cpp_source)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

add_subdirectory(pybind11)
pybind11_add_module(_ials cpp_source/als/wrapper.cpp)
#pybind11_add_module(irspack.recommenders._knn cpp_source/knn/wrapper.cpp)
#pybind11_add_module(_util_cpp cpp_source/util.cpp)
#pybind11_add_module(irspack._evapuator cpp_source/evaluator.cpp)
#pybind11_add_module(irspack._rwr cpp_source/rws.cpp)

# Detect the installed nanobind package and import it into CMake
find_package(Python 3.8 COMPONENTS Interpreter ${DEV_MODULE} REQUIRED)
execute_process(
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT)
find_package(nanobind CONFIG REQUIRED)

nanobind_add_module(_ials_core cpp_source/als/wrapper.cpp)
nanobind_add_module(_knn cpp_source/knn/wrapper.cpp)
nanobind_add_module(_util_cpp cpp_source/util.cpp)
nanobind_add_module(_core_evaluator cpp_source/evaluator.cpp)

install(TARGETS _ials_core LIBRARY DESTINATION irspack/recommenders)
install(TARGETS _core_evaluator LIBRARY DESTINATION irspack/evaluation)
install(TARGETS _util_cpp LIBRARY DESTINATION irspack/utils)
install(TARGETS _knn LIBRARY DESTINATION irspack/recommenders)
1 change: 0 additions & 1 deletion cpp_source/als/definitions.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#include <Eigen/Core>
#include <Eigen/Sparse>
#include <vector>

namespace irspack {
namespace ials {
Expand Down
137 changes: 63 additions & 74 deletions cpp_source/als/wrapper.cpp
Original file line number Diff line number Diff line change
@@ -1,64 +1,57 @@
#include "IALSLearningConfig.hpp"
#include "IALSTrainer.hpp"
#include "pybind11/cast.h"
#include <Eigen/Sparse>
#include <cstddef>
#include <pybind11/eigen.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include <sstream>
#include <stdexcept>
#include <vector>
#include <nanobind/nanobind.h>
#include <nanobind/nb_defs.h>
#include <nanobind/eigen/dense.h>
#include <nanobind/eigen/sparse.h>
#include <nanobind/stl/tuple.h>
#include <nanobind/stl/string.h>
#include <tuple>

namespace py = pybind11;
using namespace irspack::ials;
using std::vector;
using namespace nanobind;

PYBIND11_MODULE(_ials, m) {
NB_MODULE(_ials_core, m) {
std::stringstream doc_stream;
doc_stream << "irspack's core module for \"IALSRecommender\"." << std::endl
<< "Built to use" << std::endl
<< "\t" << Eigen::SimdInstructionSetsInUse();

m.doc() = doc_stream.str();

py::enum_<LossType>(m, "LossType")
nanobind::enum_<LossType>(m, "LossType")
.value("ORIGINAL", LossType::ORIGINAL)
.value("IALSPP", LossType::IALSPP)
.export_values();

py::enum_<SolverType>(m, "SolverType")
nanobind::enum_<SolverType>(m, "SolverType")
.value("CHOLESKY", SolverType::Cholesky)
.value("CG", SolverType::CG)
.value("IALSPP", SolverType::IALSPP)
.export_values();

auto model_config =
py::class_<IALSModelConfig>(m, "IALSModelConfig")
.def(py::init<size_t, Real, Real, Real, Real, int, LossType>())
.def(py::pickle(
[](const IALSModelConfig &config) {
return py::make_tuple(config.K, config.alpha0, config.reg,
config.nu, config.init_stdev,
config.random_seed, config.loss_type);
},
[](py::tuple t) {
if (t.size() != 7)
throw std::runtime_error("invalid state");

size_t K = t[0].cast<size_t>();
Real alpha0 = t[1].cast<Real>();
Real reg = t[2].cast<Real>();
Real nu = t[3].cast<Real>();
Real init_stdev = t[4].cast<Real>();
int random_seed = t[5].cast<int>();
LossType loss_type = t[6].cast<LossType>();
return IALSModelConfig(K, alpha0, reg, nu, init_stdev,
random_seed, loss_type);
}));
py::class_<IALSModelConfig::Builder>(m, "IALSModelConfigBuilder")
.def(py::init<>())
nanobind::class_<IALSModelConfig>(m, "IALSModelConfig")
.def(nanobind::init<size_t, Real, Real, Real, Real, int, LossType>())
.def("__getstate__",
[](const IALSModelConfig &config) {
return nanobind::make_tuple(
config.K, config.alpha0, config.reg, config.nu,
config.init_stdev, config.random_seed, config.loss_type);
})
.def("__setstate__",
[](IALSModelConfig &ials_model_config,
const std::tuple<size_t, Real, Real, Real, Real, int,
LossType> &state) {
new (&ials_model_config) IALSModelConfig(
std::get<0>(state), std::get<1>(state), std::get<2>(state),
std::get<3>(state), std::get<4>(state), std::get<5>(state),
std::get<6>(state));
});
nanobind::class_<IALSModelConfig::Builder>(m, "IALSModelConfigBuilder")
.def(nanobind::init<>())
.def("build", &IALSModelConfig::Builder::build)
.def("set_K", &IALSModelConfig::Builder::set_K)
.def("set_alpha0", &IALSModelConfig::Builder::set_alpha0)
Expand All @@ -69,30 +62,28 @@ PYBIND11_MODULE(_ials, m) {
.def("set_loss_type", &IALSModelConfig::Builder::set_loss_type);

auto solver_config =
py::class_<SolverConfig>(m, "IALSSolverConfig")
.def(py::init<size_t, SolverType, size_t, size_t, size_t>())
.def(py::pickle(
[](const SolverConfig &config) {
return py::make_tuple(
nanobind::class_<SolverConfig>(m, "IALSSolverConfig")
.def(nanobind::init<size_t, SolverType, size_t, size_t, size_t>())
.def(
"__getstate__", [](const SolverConfig &config) {
return std::make_tuple(
config.n_threads, config.solver_type, config.max_cg_steps,
config.ialspp_subspace_dimension, config.ialspp_iteration);
},
[](py::tuple t) {
if (t.size() != 5)
throw std::runtime_error("invalid state");

size_t n_threads = t[0].cast<size_t>();
SolverType solver_type = t[1].cast<SolverType>();
size_t max_cg_steps = t[2].cast<size_t>();
size_t ialspp_subspace_dimension = t[3].cast<size_t>();
size_t ialspp_iteration = t[4].cast<size_t>();
return SolverConfig(n_threads, solver_type, max_cg_steps,
ialspp_subspace_dimension,
ialspp_iteration);
}));
}
)
.def(
"__setstate__", [](SolverConfig &config, const std::tuple<size_t, SolverType, size_t, size_t, size_t> &state) {
new (&config) SolverConfig(
std::get<0>(state),
std::get<1>(state),
std::get<2>(state),
std::get<3>(state),
std::get<4>(state));
}
);

py::class_<SolverConfig::Builder>(m, "IALSSolverConfigBuilder")
.def(py::init<>())
nanobind::class_<SolverConfig::Builder>(m, "IALSSolverConfigBuilder")
.def(nanobind::init<>())
.def("build", &SolverConfig::Builder::build)
.def("set_n_threads", &SolverConfig::Builder::set_n_threads)
.def("set_solver_type", &SolverConfig::Builder::set_solver_type)
Expand All @@ -102,25 +93,23 @@ PYBIND11_MODULE(_ials, m) {
.def("set_ialspp_iteration",
&SolverConfig::Builder::set_ialspp_iteration);

py::class_<IALSTrainer>(m, "IALSTrainer")
.def(py::init<IALSModelConfig, const SparseMatrix &>())
nanobind::class_<IALSTrainer>(m, "IALSTrainer")
.def(nanobind::init<IALSModelConfig, const SparseMatrix &>())
.def("step", &IALSTrainer::step)
.def("user_scores", &IALSTrainer::user_scores)
.def("transform_user", &IALSTrainer::transform_user)
.def("transform_item", &IALSTrainer::transform_item)
.def("compute_loss", &IALSTrainer::compute_loss)
.def_readwrite("user", &IALSTrainer::user)
.def_readwrite("item", &IALSTrainer::item)
.def(py::pickle(
[](const IALSTrainer &trainer) {
return py::make_tuple(trainer.config_, trainer.user, trainer.item);
},
[](py::tuple t) {
if (t.size() != 3)
throw std::runtime_error("Invalid state!");
IALSTrainer trainer(t[0].cast<IALSModelConfig>(),
t[1].cast<DenseMatrix>(),
t[2].cast<DenseMatrix>());
return trainer;
}));
.def_rw("user", &IALSTrainer::user)
.def_rw("item", &IALSTrainer::item)
.def("__getstate__", [](const IALSTrainer & trainer) {
return std::make_tuple(trainer.config_, trainer.user, trainer.item);
})
.def("__setstate__", [](IALSTrainer & trainer, const std::tuple<IALSModelConfig, DenseMatrix, DenseMatrix> & state) {
new (&trainer) IALSTrainer(
std::get<0>(state), std::get<1>(state),
std::get<2>(state)

);
});
}
Loading
Loading