diff --git a/.github/workflows/doctest.yml b/.github/workflows/doctest.yml index a2a4303..39f63d6 100644 --- a/.github/workflows/doctest.yml +++ b/.github/workflows/doctest.yml @@ -6,22 +6,19 @@ jobs: env: OS: ubuntu-latest steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 + - uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: "3.13" - name: Build myfm run: | - pip install --upgrade pip - pip install numpy scipy pandas scikit-learn + pip install --upgrade pip scikit-build setuptools-scm nanobind pip install . curl http://files.grouplens.org/datasets/movielens/ml-100k.zip -o ~/.ml-100k.zip - - name: Run pytest + - name: install pytest run: | - pip install pytest phmdoctest sphinx==4.4.0 sphinx_rtd_theme + pip install pytest phmdoctest sphinx==8.2.3 sphinx_rtd_theme scikit-learn - name: Test Readme.md run: | GEN_TEST_FILE=phmdoctest_out.py diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 9cacefa..7fd4104 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -8,6 +8,6 @@ jobs: env: SKIP: no-commit-to-branch steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 - - uses: pre-commit/action@v3.0.0 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v3 + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index 6690726..d20f700 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -2,37 +2,35 @@ name: Test & Upload coverage on: [push] jobs: run_pytest_upload_coverage: + permissions: + id-token: write runs-on: ubuntu-latest env: OS: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: - python-version: "3.11" - - name: Build myfm + python-version: '3.13' + - name: Build myfm & test run: | - pip install --upgrade pip - pip install numpy scipy pandas sudo apt-get install lcov - FLAGS="-fprofile-arcs -ftest-coverage" - CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" pip install -e . - - name: Run pytest - run: | - pip install pytest pytest-cov pytest-mock + pip install --upgrade pip + pip install scikit-build setuptools-scm pytest pytest-cov pytest-mock nanobind + FLAGS="-O0 -fprofile-arcs -ftest-coverage -fprofile-update=atomic" + CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" pip install --editable . pytest --cov=./src/myfm tests/ - - name: Generate coverage (ubuntu) - run: | coverage xml - lcov -d `pwd` -c -o coverage.info + lcov -d `pwd` -c -o coverage.info --ignore-errors source - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v5 with: + use_oidc: true files: ./coverage.xml,./coverage.info - verbose: false + verbose: true env_vars: OS,PYTHON name: codecov-umbrella fail_ci_if_error: false diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 56491e0..207e174 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -7,160 +7,71 @@ on: types: - created env: - cibuildwheel_version: "2.12.2" + cibuildwheel_version: '3.0.1' jobs: build_sdist: 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.11" + python-version: '3.13' - name: Build sdist - run: pip install pybind11 && python setup.py sdist - - uses: actions/upload-artifact@v2 + 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: - MACOSX_DEPLOYMENT_TARGET: "10.9" - CIBW_BUILD_VERBOSITY: "1" - CIBW_BUILD: "${{ matrix.cibw.build || '*' }}" - CIBW_SKIP: "${{ matrix.cibw.skip || '' }}" - CIBW_ENVIRONMENT: "${{ matrix.cibw.env || '' }}" - CIBW_TEST_COMMAND: "pytest {project}/tests" - CIBW_TEST_REQUIRES: pytest pytest-mock - CIBW_MANYLINUX_X86_64_IMAGE: "${{ matrix.cibw.manylinux_image }}" - CIBW_MANYLINUX_I686_IMAGE: "${{ matrix.cibw.manylinux_image }}" - CIBW_MANYLINUX_AARCH64_IMAGE: "${{ matrix.cibw.manylinux_image }}" - CIBW_ARCHS_LINUX: "${{ matrix.cibw.arch || 'auto' }}" - CIBW_ARCHS_MACOS: "${{ matrix.cibw.arch || 'auto' }}" + runs-on: ${{ matrix.runs-on }} strategy: matrix: include: - - os: macos-10.15 - name: mac - cibw: - arch: x86_64 - build: "cp37* cp38*" - - - os: macos-10.15 - name: mac-arm - cibw: - arch: universal2 - build: "cp39* cp310* cp311*" - - - os: ubuntu-20.04 - name: manylinux1 - cibw: - build: "cp37*" - skip: "*musllinux*" - manylinux_image: manylinux2010 - arch: auto64 - - - os: ubuntu-20.04 - name: manylinux2014 - cibw: - build: "cp38* cp39* cp310* cp311*" - skip: "*musllinux*" - manylinux_image: manylinux2014 - arch: auto64 - - - os: ubuntu-20.04 - name: manylinux_aarch64_cp37 - cibw: - build: "cp37*" - skip: "*musllinux*" - manylinux_image: manylinux2014 - arch: aarch64 - - - os: ubuntu-20.04 - name: manylinux_aarch64_cp38 - cibw: - build: "cp38*" - skip: "*musllinux*" - manylinux_image: manylinux2014 - arch: aarch64 - - - os: ubuntu-20.04 - name: manylinux_aarch64_cp39 - cibw: - build: "cp39*" - skip: "*musllinux*" - manylinux_image: manylinux2014 - arch: aarch64 - - - os: ubuntu-20.04 - name: manylinux_aarch64_cp310 - cibw: - build: "cp310*" - skip: "*musllinux*" - manylinux_image: manylinux2014 - arch: aarch64 - - - os: ubuntu-20.04 - name: manylinux_aarch64_cp311 - cibw: - build: "cp311*" - skip: "*musllinux*" - manylinux_image: manylinux2014 - arch: aarch64 - - - os: windows-2019 - name: win_amd64 - architecture: x64 - cibw: - skip: "cp36*" - build: "cp*win_amd64" - + - os: linux-intel + runs-on: ubuntu-latest + - os: linux-arm + runs-on: ubuntu-24.04-arm + - os: windows-intel + runs-on: windows-latest + - os: macos-intel + runs-on: macos-13 + - os: macos-arm + runs-on: macos-latest steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/setup-python@v2 - name: Install Python - - name: register qemu - if: contains(matrix.cibw.arch, 'aarch64') - run: | - docker run --rm --privileged hypriot/qemu-register:v4.2.0 - - name: Install cibuildwheel - run: python -m pip install cibuildwheel=="${{env.cibuildwheel_version}}" + - uses: actions/checkout@v4 - name: Build wheels - run: python -m cibuildwheel --output-dir wheelhouse - - - uses: actions/upload-artifact@v2 + uses: pypa/cibuildwheel@v3.0.1 + env: + CIBW_PLATFORM: ${{ matrix.platform || 'auto' }} + - 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/ + packages-dir: dist/ + repository-url: https://test.pypi.org/legacy/ verbose: true - skip_existing: true + skip-existing: true + attestations: false - 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/ + packages-dir: dist/ verbose: true - skip_existing: true + skip-existing: true diff --git a/.gitignore b/.gitignore index 507cca9..1032915 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,8 @@ stubs/* doc/source/api_reference/*.rst .cache +src/myfm/_version.py + +mise.toml + +.venv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 37e88d0..816df98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v4.6.0 hooks: - id: check-merge-conflict - id: check-yaml @@ -10,20 +10,13 @@ repos: - id: no-commit-to-branch args: [--branch, main] - id: trailing-whitespace - - id: end-of-file-fixer - id: check-added-large-files - repo: https://github.com/PyCQA/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort name: isort - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 24.10.0 hooks: - id: black - language_version: python3 # Should be a command that runs python3.6+ - - repo: https://github.com/hadialqattan/pycln - rev: v1.1.0 - hooks: - - id: pycln - args: [--config=pyproject.toml] diff --git a/CMakeLists.txt b/CMakeLists.txt index 12171ef..d42fd77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,42 @@ -set(CMAKE_C_COMPILER gcc) -set(CMAKE_CXX_COMPILER g++) +cmake_minimum_required(VERSION 3.15...3.27) +project(myFM VERSION 0.1.0) -cmake_minimum_required(VERSION 3.0.0) -project(myfm) +if (CMAKE_VERSION VERSION_LESS 3.18) + set(DEV_MODULE Development) +else() + set(DEV_MODULE Development.Module) +endif() -set(CMAKE_BUILD_TYPE Release) -set(CMAKE_CXX_FLAGS " ${CMAKE_CXX_FLAGS_INIT} -std=c++11 -fPIC") -add_subdirectory(pybind11) -include_directories(include eigen-3.3.7) -pybind11_add_module(_myfm src/bind.cpp src/Faddeeva.cc) +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("${CMAKE_BINARY_DIR}/eigen-3.4.0" cpp_source) +include_directories("${CMAKE_SOURCE_DIR}/include" cpp_source) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CPACK_PROJECT_NAME ${PROJECT_NAME}) set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +include(CPack) + + +# 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(_myfm cpp_source/bind.cpp cpp_source/Faddeeva.cc) + +install(TARGETS _myfm LIBRARY DESTINATION myfm/) diff --git a/README.md b/README.md index ec8a297..3fd9ef8 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ X_movie = movie_ohe.transform( block_user = RelationBlock(user_indices, X_user) block_movie = RelationBlock(movie_indices, X_movie) -fm = MyFMRegressor(rank=2).fit(None, ratings.rating, X_rel=[block_user, block_movie]) +fm = MyFMRegressor(rank=2).fit(None, ratings.rating.values, X_rel=[block_user, block_movie]) prediction_df = pd.DataFrame([ dict(user_id=user_id,movie_id=movie_id, diff --git a/cpp_source/bind.cpp b/cpp_source/bind.cpp index 5cda363..b8de5b3 100644 --- a/cpp_source/bind.cpp +++ b/cpp_source/bind.cpp @@ -1,5 +1,7 @@ #include "declare_module.hpp" +#include +#include -PYBIND11_MODULE(_myfm, m) { +NB_MODULE(_myfm, m) { declare_functional(m); } diff --git a/cpp_source/declare_module.hpp b/cpp_source/declare_module.hpp index fca1f05..a1a888a 100644 --- a/cpp_source/declare_module.hpp +++ b/cpp_source/declare_module.hpp @@ -2,29 +2,29 @@ #include #include -#include -#include #include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "myfm/FM.hpp" #include "myfm/FMLearningConfig.hpp" #include "myfm/FMTrainer.hpp" #include "myfm/LearningHistory.hpp" -#include "myfm/OProbitSampler.hpp" #include "myfm/definitions.hpp" #include "myfm/util.hpp" #include "myfm/variational.hpp" using namespace std; -namespace py = pybind11; - template using FMTrainer = myFM::GibbsFMTrainer; template @@ -64,7 +64,7 @@ create_train_vfm( return fm_trainer.learn_with_callback(fm, hyper_param, cb); } -template void declare_functional(py::module &m) { +template void declare_functional(nanobind::module_ &m) { using FMTrainer = FMTrainer; using VFMTrainer = myFM::variational::VariationalFMTrainer; using FM = myFM::FM; @@ -85,16 +85,16 @@ template void declare_functional(py::module &m) { m.doc() = "Backend C++ implementation for myfm."; - py::enum_(m, "TaskType", py::arithmetic()) + nanobind::enum_(m, "TaskType") .value("REGRESSION", TASKTYPE::REGRESSION) .value("CLASSIFICATION", TASKTYPE::CLASSIFICATION) .value("ORDERED", TASKTYPE::ORDERED); - py::class_(m, "FMLearningConfig"); + nanobind::class_(m, "FMLearningConfig"); - py::class_(m, "RelationBlock", - R"delim(The RelationBlock Class.)delim") - .def(py::init, const SparseMatrix &>(), R"delim( + nanobind::class_(m, "RelationBlock", + R"delim(The RelationBlock Class.)delim") + .def(nanobind::init, const SparseMatrix &>(), R"delim( Initializes relation block. Parameters @@ -108,12 +108,12 @@ template void declare_functional(py::module &m) { Note ----- The entries of `original_to_block` must be in the [0, data.shape[0]-1].)delim", - py::arg("original_to_block"), py::arg("data")) - .def_readonly("original_to_block", &RelationBlock::original_to_block) - .def_readonly("data", &RelationBlock::X) - .def_readonly("mapper_size", &RelationBlock::mapper_size) - .def_readonly("block_size", &RelationBlock::block_size) - .def_readonly("feature_size", &RelationBlock::feature_size) + nanobind::arg("original_to_block"), nanobind::arg("data")) + .def_ro("original_to_block", &RelationBlock::original_to_block) + .def_ro("data", &RelationBlock::X) + .def_ro("mapper_size", &RelationBlock::mapper_size) + .def_ro("block_size", &RelationBlock::block_size) + .def_ro("feature_size", &RelationBlock::feature_size) .def("__repr__", [](const RelationBlock &block) { return (myFM::StringBuilder{})( @@ -123,21 +123,19 @@ template void declare_functional(py::module &m) { block.feature_size)(">") .build(); }) - .def(py::pickle( - [](const RelationBlock &block) { - return py::make_tuple(block.original_to_block, block.X); - }, - [](py::tuple t) { - if (t.size() != 2) { - throw std::runtime_error("invalid state for Relationblock."); - } - return new RelationBlock( - t[0].cast>(), - t[1].cast()); - })); + .def("__getstate__", + [](const RelationBlock &block) { + return std::make_tuple(block.original_to_block, block.X); + }) + .def("__setstate__", + [](RelationBlock &block, + const std::tuple, + typename RelationBlock::SparseMatrix> &state) { + new (&block) RelationBlock(std::get<0>(state), std::get<1>(state)); + }); - py::class_(m, "ConfigBuilder") - .def(py::init<>()) + nanobind::class_(m, "ConfigBuilder") + .def(nanobind::init<>()) .def("set_alpha_0", &ConfigBuilder::set_alpha_0) .def("set_beta_0", &ConfigBuilder::set_beta_0) .def("set_gamma_0", &ConfigBuilder::set_gamma_0) @@ -155,11 +153,11 @@ template void declare_functional(py::module &m) { .def("set_cutpoint_groups", &ConfigBuilder::set_cutpoint_groups) .def("build", &ConfigBuilder::build); - py::class_(m, "FM") - .def_readwrite("w0", &FM::w0) - .def_readwrite("w", &FM::w) - .def_readwrite("V", &FM::V) - .def_readwrite("cutpoints", &FM::cutpoints) + nanobind::class_(m, "FM") + .def_rw("w0", &FM::w0) + .def_rw("w", &FM::w) + .def_rw("V", &FM::V) + .def_rw("cutpoints", &FM::cutpoints) .def("predict_score", &FM::predict_score) .def("oprobit_predict_proba", &FM::oprobit_predict_proba) .def("__repr__", @@ -169,36 +167,29 @@ template void declare_functional(py::module &m) { fm.w.rows())(", rank = ")(fm.V.cols())(">") .build(); }) - .def(py::pickle( - [](const FM &fm) { - Real w0 = fm.w0; - Vector w(fm.w); - DenseMatrix V(fm.V); - vector cutpoints(fm.cutpoints); - return py::make_tuple(w0, w, V, cutpoints); - }, - [](py::tuple t) { - if (t.size() == 3) { - /* For the compatibility with earlier versions */ - return new FM(t[0].cast(), t[1].cast(), - t[2].cast()); - } else if (t.size() == 4) { - return new FM(t[0].cast(), t[1].cast(), - t[2].cast(), - t[3].cast>()); - } else { - throw std::runtime_error("invalid state for FM."); - } - })); + .def("__getstate__", + [](const FM &fm) { + Real w0 = fm.w0; + Vector w(fm.w); + DenseMatrix V(fm.V); + vector cutpoints(fm.cutpoints); + return std::make_tuple(w0, w, V, cutpoints); + }) + .def("__setstate__", + [](FM &fm, const std::tuple> &state) { + new (&fm) FM(std::get<0>(state), std::get<1>(state), + std::get<2>(state), std::get<3>(state)); + }); - py::class_(m, "VariationalFM") - .def_readwrite("w0", &VFM::w0) - .def_readwrite("w0_var", &VFM::w0_var) - .def_readwrite("w", &VFM::w) - .def_readwrite("w_var", &VFM::w_var) - .def_readwrite("V", &VFM::V) - .def_readwrite("V_var", &VFM::V_var) - .def_readwrite("cutpoints", &VFM::cutpoints) + nanobind::class_(m, "VariationalFM") + .def_rw("w0", &VFM::w0) + .def_rw("w0_var", &VFM::w0_var) + .def_rw("w", &VFM::w) + .def_rw("w_var", &VFM::w_var) + .def_rw("V", &VFM::V) + .def_rw("V_var", &VFM::V_var) + .def_rw("cutpoints", &VFM::cutpoints) .def("predict_score", &VFM::predict_score) .def("__repr__", [](const VFM &fm) { @@ -207,196 +198,178 @@ template void declare_functional(py::module &m) { fm.w.rows())(", rank = ")(fm.V.cols())(">") .build(); }) - .def(py::pickle( - [](const VFM &fm) { - Real w0 = fm.w0; - Real w0_var = fm.w0_var; - Vector w(fm.w); - Vector w_var(fm.w_var); - DenseMatrix V(fm.V); - DenseMatrix V_var(fm.V_var); - vector cutpoints(fm.cutpoints); - return py::make_tuple(w0, w0_var, w, w_var, V, V_var, cutpoints); - }, - [](py::tuple t) { - if (t.size() == 6) { - /* For the compatibility with earlier versions */ - return new VFM(t[0].cast(), t[1].cast(), - t[2].cast(), t[3].cast(), - t[4].cast(), - t[5].cast()); - } else if (t.size() == 7) { - return new VFM(t[0].cast(), t[1].cast(), - t[2].cast(), t[3].cast(), - t[4].cast(), t[5].cast(), - t[6].cast>()); - } else { - throw std::runtime_error("invalid state for FM."); - } - })); + .def("__getstate__", + [](const VFM &fm) { + Real w0 = fm.w0; + Real w0_var = fm.w0_var; + Vector w(fm.w); + Vector w_var(fm.w_var); + DenseMatrix V(fm.V); + DenseMatrix V_var(fm.V_var); + vector cutpoints(fm.cutpoints); + return std::make_tuple(w0, w0_var, w, w_var, V, V_var, cutpoints); + }) + .def( + "__setstate__", + [](VFM &vfm, const std::tuple> &state) { + new (&vfm) + VFM(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) - py::class_(m, "FMHyperParameters") - .def_readonly("alpha", &Hyper::alpha) - .def_readonly("mu_w", &Hyper::mu_w) - .def_readonly("lambda_w", &Hyper::lambda_w) - .def_readonly("mu_V", &Hyper::mu_V) - .def_readonly("lambda_V", &Hyper::lambda_V) - .def(py::pickle( - [](const Hyper &hyper) { - Real alpha = hyper.alpha; - Vector mu_w(hyper.mu_w); - Vector lambda_w(hyper.lambda_w); - DenseMatrix mu_V(hyper.mu_V); - DenseMatrix lambda_V(hyper.lambda_V); - return py::make_tuple(alpha, mu_w, lambda_w, mu_V, lambda_V); - }, - [](py::tuple t) { - if (t.size() != 5) { - throw std::runtime_error("invalid state for FMHyperParameters."); - } - // placement new - return new Hyper(t[0].cast(), t[1].cast(), - t[2].cast(), t[3].cast(), - t[4].cast()); - })); + ); + }); - py::class_(m, "VariationalFMHyperParameters") - .def_readonly("alpha", &VHyper::alpha) - .def_readonly("alpha_rate", &VHyper::alpha_rate) - .def_readonly("mu_w", &VHyper::mu_w) - .def_readonly("mu_w_var", &VHyper::mu_w_var) - .def_readonly("lambda_w", &VHyper::lambda_w) - .def_readonly("lambda_w_rate", &VHyper::lambda_w_rate) - .def_readonly("mu_V", &VHyper::mu_V) - .def_readonly("mu_V_var", &VHyper::mu_V_var) - .def_readonly("lambda_V", &VHyper::lambda_V) - .def_readonly("lambda_V_rate", &VHyper::lambda_V_rate) - .def(py::pickle( - [](const VHyper &hyper) { - Real alpha = hyper.alpha; - Real alpha_rate = hyper.alpha_rate; - Vector mu_w(hyper.mu_w); - Vector mu_w_var(hyper.mu_w_var); - Vector lambda_w(hyper.lambda_w); - Vector lambda_w_rate(hyper.lambda_w_rate); - DenseMatrix mu_V(hyper.mu_V); - DenseMatrix mu_V_var(hyper.mu_V_var); - DenseMatrix lambda_V(hyper.lambda_V); - DenseMatrix lambda_V_rate(hyper.lambda_V_rate); + nanobind::class_(m, "FMHyperParameters") + .def_ro("alpha", &Hyper::alpha) + .def_ro("mu_w", &Hyper::mu_w) + .def_ro("lambda_w", &Hyper::lambda_w) + .def_ro("mu_V", &Hyper::mu_V) + .def_ro("lambda_V", &Hyper::lambda_V) + .def("__getstate__", + [](const Hyper &hyper) { + Real alpha = hyper.alpha; + Vector mu_w(hyper.mu_w); + Vector lambda_w(hyper.lambda_w); + DenseMatrix mu_V(hyper.mu_V); + DenseMatrix lambda_V(hyper.lambda_V); + return std::make_tuple(alpha, mu_w, lambda_w, mu_V, lambda_V); + }) + .def("__setstate__", + [](Hyper &hyper, const std::tuple &state) { + new (&hyper) Hyper(std::get<0>(state), std::get<1>(state), + std::get<2>(state), std::get<3>(state), + std::get<4>(state)); + }); - return py::make_tuple(alpha, alpha_rate, mu_w, mu_w_var, lambda_w, - lambda_w_rate, mu_V, mu_V_var, lambda_V, - lambda_V_rate); - }, - [](py::tuple t) { - if (t.size() != 10) { - throw std::runtime_error("invalid state for FMHyperParameters."); - } - // placement new - return new VHyper( - t[0].cast(), t[1].cast(), t[2].cast(), - t[3].cast(), t[4].cast(), t[5].cast(), - t[6].cast(), t[7].cast(), - t[8].cast(), t[9].cast()); - })); + nanobind::class_(m, "VariationalFMHyperParameters") + .def_ro("alpha", &VHyper::alpha) + .def_ro("alpha_rate", &VHyper::alpha_rate) + .def_ro("mu_w", &VHyper::mu_w) + .def_ro("mu_w_var", &VHyper::mu_w_var) + .def_ro("lambda_w", &VHyper::lambda_w) + .def_ro("lambda_w_rate", &VHyper::lambda_w_rate) + .def_ro("mu_V", &VHyper::mu_V) + .def_ro("mu_V_var", &VHyper::mu_V_var) + .def_ro("lambda_V", &VHyper::lambda_V) + .def_ro("lambda_V_rate", &VHyper::lambda_V_rate) + .def("__getstate__", + [](const VHyper &hyper) { + Real alpha = hyper.alpha; + Real alpha_rate = hyper.alpha_rate; + Vector mu_w(hyper.mu_w); + Vector mu_w_var(hyper.mu_w_var); + Vector lambda_w(hyper.lambda_w); + Vector lambda_w_rate(hyper.lambda_w_rate); + DenseMatrix mu_V(hyper.mu_V); + DenseMatrix mu_V_var(hyper.mu_V_var); + DenseMatrix lambda_V(hyper.lambda_V); + DenseMatrix lambda_V_rate(hyper.lambda_V_rate); - py::class_(m, "Predictor") - .def_readonly("samples", &Predictor::samples) + return nanobind::make_tuple(alpha, alpha_rate, mu_w, mu_w_var, + lambda_w, lambda_w_rate, mu_V, + mu_V_var, lambda_V, lambda_V_rate); + }) + .def( + "__setstate__", + [](VHyper &hyper, const std::tuple &state) { + new (&hyper) VHyper(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), std::get<7>(state), + std::get<8>(state), std::get<9>(state)); + }); + + nanobind::class_(m, "Predictor") + .def_ro("samples", &Predictor::samples) .def("predict", &Predictor::predict) .def("predict_parallel", &Predictor::predict_parallel) .def("predict_parallel_oprobit", &Predictor::predict_parallel_oprobit) - .def(py::pickle( - [](const Predictor &predictor) { - return py::make_tuple(predictor.rank, predictor.feature_size, - static_cast(predictor.type), - predictor.samples); - }, - [](py::tuple t) { - if (t.size() != 4) { - throw std::runtime_error("invalid state for FMHyperParameters."); - } - Predictor *p = - new Predictor(t[0].cast(), t[1].cast(), - static_cast(t[2].cast())); - p->set_samples(std::move(t[3].cast>())); - return p; - })); - - py::class_(m, "VariationalPredictor") + .def("__getstate__", + [](const Predictor &predictor) { + return std::make_tuple(predictor.rank, predictor.feature_size, + static_cast(predictor.type), + predictor.samples); + }) + .def("__setstate__", + [](Predictor &predictor, + const std::tuple> &state) { + new (&predictor) Predictor(std::get<0>(state), std::get<1>(state), + std::get<2>(state), std::get<3>(state)); + }); + nanobind::class_(m, "VariationalPredictor") .def("predict", &VPredictor::predict) - .def(py::pickle( - [](const VPredictor &predictor) { - return py::make_tuple(predictor.rank, predictor.feature_size, - static_cast(predictor.type), - predictor.samples); - }, - [](py::tuple t) { - if (t.size() != 4) { - throw std::runtime_error("invalid state for FMHyperParameters."); - } - VPredictor *p = - new VPredictor(t[0].cast(), t[1].cast(), - static_cast(t[2].cast())); - p->set_samples(std::move(t[3].cast>())); - return p; - })) - .def("weights", [](VPredictor &predictor) { + .def("weights", [](const VPredictor &predictor) { VFM returned = predictor.samples.at(0); return returned; - }); - - py::class_(m, "FMTrainer") - .def(py::init &, - const Vector &, int, FMLearningConfig>()) + }) + .def("__getstate__", + [](const VPredictor &predictor) { + return std::make_tuple(predictor.rank, predictor.feature_size, + static_cast(predictor.type), + predictor.samples); + }) + .def("__setstate__", + [](VPredictor &predictor, + const std::tuple> &state) { + new (&predictor) VPredictor(std::get<0>(state), std::get<1>(state), + std::get<2>(state), std::get<3>(state)); + }); + nanobind::class_(m, "FMTrainer") + .def(nanobind::init &, + const Vector &, int, FMLearningConfig>()) .def("create_FM", &FMTrainer::create_FM) .def("create_Hyper", &FMTrainer::create_Hyper); - py::class_(m, "VariationalFMTrainer") - .def(py::init &, - const Vector &, int, FMLearningConfig>()) + nanobind::class_(m, "VariationalFMTrainer") + .def(nanobind::init &, + const Vector &, int, FMLearningConfig>()) .def("create_FM", &VFMTrainer::create_FM) .def("create_Hyper", &VFMTrainer::create_Hyper); - py::class_(m, "LearningHistory") - .def_readonly("hypers", &History::hypers) - .def_readonly("train_log_losses", &History::train_log_losses) - .def_readonly("n_mh_accept", &History::n_mh_accept) - .def(py::pickle( - [](const History &h) { - return py::make_tuple(h.hypers, h.train_log_losses, h.n_mh_accept); - }, - [](py::tuple t) { - if (t.size() != 3) { - throw std::runtime_error("invalid state for LearningHistory."); - } - History *result = new History(); - result->hypers = t[0].cast>(); - result->train_log_losses = t[1].cast>(); - result->n_mh_accept = t[2].cast>(); - return result; - })); + nanobind::class_(m, "LearningHistory") + .def_ro("hypers", &History::hypers) + .def_ro("train_log_losses", &History::train_log_losses) + .def_ro("n_mh_accept", &History::n_mh_accept) + .def("__getstate__", + [](const History &h) { + return std::make_tuple(h.hypers, h.train_log_losses, + h.n_mh_accept); + }) + .def("__setstate__", + [](History &h, const std::tuple, vector, + vector> &state) { + History *result = new History(); + new (&h) History(); + h.hypers = std::get<0>(state); + h.train_log_losses = std::get<1>(state); + h.n_mh_accept = std::get<2>(state); + }); + + nanobind::class_(m, "VariationalLearningHistory") + .def_ro("hypers", &VHistory::hyper) + .def_ro("elbos", &VHistory::elbos) + .def("__getstate__", + [](const VHistory &h) { return std::make_tuple(h.hyper, h.elbos); }) + .def("__setstate__", + + [](VHistory &result, const std::tuple> &state) { + new (&result) VHistory(std::get<0>(state), std::get<1>(state) - py::class_(m, "VariationalLearningHistory") - .def_readonly("hypers", &VHistory::hyper) - .def_readonly("elbos", &VHistory::elbos) - .def(py::pickle( - [](const VHistory &h) { return py::make_tuple(h.hyper, h.elbos); }, - [](py::tuple t) { - if (t.size() != 2) { - throw std::runtime_error( - "invalid state for VariationalLearningHistory."); - } - VHistory *result = - new VHistory(t[0].cast(), t[1].cast>()); - return result; - })); + ); + }); m.def("create_train_fm", &create_train_fm, "create and train fm.", - py::return_value_policy::move); + nanobind::rv_policy::move); m.def("create_train_vfm", &create_train_vfm, "create and train fm.", - py::return_value_policy::move, py::arg("rank"), py::arg("init_std"), - py::arg("X"), py::arg("relations"), py::arg("y"), - py::arg("random_seed"), py::arg("learning_config"), - py::arg("callback")); + nanobind::rv_policy::move, nanobind::arg("rank"), + nanobind::arg("init_std"), nanobind::arg("X"), + nanobind::arg("relations"), nanobind::arg("y"), + nanobind::arg("random_seed"), nanobind::arg("learning_config"), + nanobind::arg("callback")); m.def("mean_var_truncated_normal_left", &myFM::mean_var_truncated_normal_left); m.def("mean_var_truncated_normal_right", diff --git a/create_nb_stub.sh b/create_nb_stub.sh new file mode 100644 index 0000000..d506335 --- /dev/null +++ b/create_nb_stub.sh @@ -0,0 +1,10 @@ +#!/bin/bash +modules=( \ +"myfm._myfm" +) +for module_name in "${modules[@]}" +do + echo "Create stub for $module_name" + output_path="src/$(echo "${module_name}" | sed 's/\./\//g').pyi" + python -m "nanobind.stubgen" -m "$module_name" -o "$output_path" +done diff --git a/doc/source/relation-blocks.rst b/doc/source/relation-blocks.rst index 2737f52..36a2010 100644 --- a/doc/source/relation-blocks.rst +++ b/doc/source/relation-blocks.rst @@ -156,7 +156,7 @@ We now setup the problem in a non-relational way: movie_data_test[test_mid_index] ]) - fm_naive = myfm.MyFMRegressor(rank=10).fit(X_train_naive, df_train.rating, n_iter=3, n_kept_samples=3) + fm_naive = myfm.MyFMRegressor(rank=10).fit(X_train_naive, df_train.rating.values, n_iter=3, n_kept_samples=3) In my environment, it takes ~ 2s per iteration, which is much slower than pure MF example. diff --git a/examples/oprobit_example.py b/examples/oprobit_example.py index abab773..5fbe53a 100644 --- a/examples/oprobit_example.py +++ b/examples/oprobit_example.py @@ -27,5 +27,5 @@ n_iter=11000, n_kept_samples=10000, ) - +assert fm.cutpoint_samples is not None print(fm.cutpoint_samples.mean(axis=0)) diff --git a/include/myfm/FMLearningConfig.hpp b/include/myfm/FMLearningConfig.hpp index f26bd26..3f0e8af 100644 --- a/include/myfm/FMLearningConfig.hpp +++ b/include/myfm/FMLearningConfig.hpp @@ -107,83 +107,69 @@ template struct FMLearningConfig { Builder() {} - inline Builder &set_alpha_0(Real arg) { + inline void set_alpha_0(Real arg) { this->alpha_0 = arg; - return *this; } - inline Builder &set_beta_0(Real arg) { + inline void set_beta_0(Real arg) { this->beta_0 = arg; - return *this; } - inline Builder &set_gamma_0(Real arg) { + inline void set_gamma_0(Real arg) { this->gamma_0 = arg; - return *this; } - inline Builder &set_mu_0(Real arg) { + inline void set_mu_0(Real arg) { this->mu_0 = arg; - return *this; } - inline Builder &set_reg_0(Real arg) { + inline void set_reg_0(Real arg) { this->reg_0 = arg; - return *this; } - inline Builder &set_n_iter(int arg) { + inline void set_n_iter(int arg) { this->n_iter = arg; - return *this; } - inline Builder &set_n_kept_samples(int arg) { + inline void set_n_kept_samples(int arg) { this->n_kept_samples = arg; - return *this; } - inline Builder &set_task_type(TASKTYPE arg) { + inline void set_task_type(TASKTYPE arg) { this->task_type = arg; - return *this; } - inline Builder &set_group_index(const vector arg) { + inline void set_group_index(const vector arg) { this->group_index = arg; - return *this; } - inline Builder &set_identical_groups(size_t n_features) { + inline void set_identical_groups(size_t n_features) { vector default_group_index(n_features); for (auto c = default_group_index.begin(); c != default_group_index.end(); c++) { *c = 0; } - return set_group_index(default_group_index); + set_group_index(default_group_index); } - inline Builder &set_nu_oprobit(size_t nu_oprobit) { + inline void set_nu_oprobit(size_t nu_oprobit) { this->nu_oprobit = nu_oprobit; - return *this; } - inline Builder &set_fit_w0(bool fit_w0) { + inline void set_fit_w0(bool fit_w0) { this->fit_w0 = fit_w0; - return *this; } - inline Builder &set_fit_linear(bool fit_linear) { + inline void set_fit_linear(bool fit_linear) { this->fit_linear = fit_linear; - return *this; } - inline Builder &set_cutpoint_scale(Real cutpoint_scale) { + inline void set_cutpoint_scale(Real cutpoint_scale) { this->cutpoint_scale = cutpoint_scale; - return *this; } - inline Builder & + inline void set_cutpoint_groups(const CutpointGroupType &cutpoint_groups) { this->cutpoint_groups = cutpoint_groups; - return *this; } FMLearningConfig build() { diff --git a/include/myfm/predictor.hpp b/include/myfm/predictor.hpp index d3ddd8e..fb24fe4 100644 --- a/include/myfm/predictor.hpp +++ b/include/myfm/predictor.hpp @@ -6,7 +6,6 @@ #include "FM.hpp" #include "FMLearningConfig.hpp" -#include "definitions.hpp" #include "util.hpp" namespace myFM { @@ -21,6 +20,9 @@ template > struct Predictor { inline Predictor(size_t rank, size_t feature_size, TASKTYPE type) : rank(rank), feature_size(feature_size), type(type), samples() {} + inline Predictor(size_t rank, size_t feature_size, TASKTYPE type, const vector & samples) + : rank(rank), feature_size(feature_size), type(type), samples(samples) {} + inline void check_input(const SparseMatrix &X, const vector &relations) const { auto given_feature_size = check_row_consistency_return_column(X, relations); @@ -146,10 +148,6 @@ template > struct Predictor { return result; } - inline void set_samples(vector &&samples_from) { - samples = std::forward>(samples_from); - } - inline void add_sample(const FMType &fm) { if (fm.w0.rows() != feature_size) { throw std::invalid_argument("feature size mismatch!"); diff --git a/include/myfm/variational.hpp b/include/myfm/variational.hpp index 789c5a7..efeb6bb 100644 --- a/include/myfm/variational.hpp +++ b/include/myfm/variational.hpp @@ -1,12 +1,7 @@ #pragma once -#include #include -#include -#include -#include #include -#include #include #include "FM.hpp" diff --git a/mypy.ini b/mypy.ini index 8d5c290..2727156 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,7 +2,7 @@ # Specify the target platform details in config, so your developers are # free to run mypy on Windows, Linux, or macOS and get consistent # results. -python_version=3.6 +python_version=3.11 platform=linux show_column_numbers=True @@ -27,5 +27,3 @@ check_untyped_defs=True # No incremental mode cache_dir=/dev/null - -plugins = numpy.typing.mypy_plugin diff --git a/pyproject.toml b/pyproject.toml index 98476cb..d64008f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,43 @@ -[build-system] -requires = [ - "setuptools>=42", - "wheel", - "pybind11>=2.8.0", - "httpx", - "setuptools_scm[toml]>=6.2", +[project] +name = "myfm" +license = "MIT" +dynamic = ["version"] +description = "Yet another Bayesian factorization machines." +readme = "README.md" +authors = [ + { name = "Tomoki Ohtsuki", email = "tomoki.ohtsuki.19937@outlook.jp" }, +] +dependencies = [ + "numpy>=2.0", + "scipy>=1.0", + "tqdm>=4", + "pandas>=2.2.0", + "typing-extensions>=4.0.0", ] -build-backend = "setuptools.build_meta" +[project.urls] +Homepage = "https://github.com/tohtsky/myFM" + + + +[build-system] +requires = ["scikit-build-core >=0.4.3", "nanobind >=1.3.2", "requests", "setuptools_scm", "setuptools>=64", "wheel"] +build-backend = "scikit_build_core.build" + +[tool.scikit-build] +minimum-version = "0.4" +build-dir = "build/{wheel_tag}" +wheel.py-api = "cp312" +metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" +sdist.include = ["src/myfm/_version.py"] + + +[tool.setuptools_scm] +write_to = "src/myfm/_version.py" +local_scheme = "no-local-version" + [tool.black] -ensure_newline_before_comments = true -force_grid_wrap = 0 -include_trailing_comma = true -line_length = 88 -multi_line_output = 3 -use_parentheses = true [tool.isort] ensure_newline_before_comments = true @@ -28,3 +50,17 @@ use_parentheses = true [tool.pycln] all = true + + +[tool.cibuildwheel] +build-verbosity = 1 +skip = ["cp38-*", "pp38-*"] +archs = ["auto64"] + +[tool.cibuildwheel.linux] +# Export a variable +environment-pass = ["CFLAGS", "CXXFLAGS"] + +# Needed for full C++17 support +[tool.cibuildwheel.macos.environment] +MACOSX_DEPLOYMENT_TARGET = "10.14" diff --git a/setup.py b/setup.py deleted file mode 100644 index 4c26abd..0000000 --- a/setup.py +++ /dev/null @@ -1,101 +0,0 @@ -import os -from pathlib import Path -from typing import Any - -from pybind11.setup_helpers import Pybind11Extension, build_ext -from setuptools import find_packages, setup - -install_requires = [ - "numpy>=1.11", - "scipy>=1.0", - "tqdm>=4", - "pandas>=1.0.0", - "typing-extensions>=4.0.0", -] - -CURRENT_DIR = Path(__file__).resolve().parent -README_FILE = CURRENT_DIR / "README.md" - - -class get_eigen_include(object): - EIGEN3_URL = "https://gitlab.com/libeigen/eigen/-/archive/3.4.0/eigen-3.4.0.zip" - EIGEN3_DIRNAME = "eigen-3.4.0" - - def __str__(self) -> str: - eigen_include_dir = os.environ.get("EIGEN3_INCLUDE_DIR", None) - if eigen_include_dir is not None: - return eigen_include_dir - - basedir = Path(__file__).resolve().parent - target_dir = basedir / self.EIGEN3_DIRNAME - if target_dir.exists(): - return str(target_dir) - - download_target_dir = basedir / "eigen3.zip" - import zipfile - - import httpx - - print("Start downloading Eigen library from {}.".format(self.EIGEN3_DIRNAME)) - with httpx.stream("GET", self.EIGEN3_URL, verify=False) as response: - with download_target_dir.open("wb") as ofs: - for chunk in response.iter_bytes(chunk_size=1024): - ofs.write(chunk) - print("Downloaded Eigen into {}.".format(download_target_dir)) - - with zipfile.ZipFile(download_target_dir) as ifs: - ifs.extractall() - - return str(target_dir) - - -headers = [ - "include/myfm/definitions.hpp", - "include/myfm/util.hpp", - "include/myfm/FM.hpp", - "include/myfm/HyperParams.hpp", - "include/myfm/predictor.hpp", - "include/myfm/FMTrainer.hpp", - "include/myfm/FMLearningConfig.hpp", - "include/myfm/OProbitSampler.hpp", - "include/Faddeeva/Faddeeva.hh", - "cpp_source/declare_module.hpp", -] - - -ext_modules = [ - Pybind11Extension( - "myfm._myfm", - ["cpp_source/bind.cpp", "cpp_source/Faddeeva.cc"], - include_dirs=[ - # Path to pybind11 headers - get_eigen_include(), - "include", - ], - ), -] - - -def local_scheme(version: Any) -> str: - return "" - - -setup( - name="myfm", - use_scm_version={"local_scheme": local_scheme}, - author="Tomoki Ohtsuki", - url="https://github.com/tohtsky/myfm", - author_email="tomoki.ohtsuki.19937@outlook.jp", - description="Yet another Bayesian factorization machines.", - long_description=README_FILE.read_text(), - long_description_content_type="text/markdown", - ext_modules=ext_modules, - install_requires=install_requires, - cmdclass={"build_ext": build_ext}, - package_dir={"": "src"}, - zip_safe=False, - headers=headers, - python_requires=">=3.6", - packages=find_packages("src"), - package_data={"myfm": ["*.pyi"]}, -) diff --git a/src/myfm/__init__.py b/src/myfm/__init__.py index 05a7b78..d5b9a5e 100644 --- a/src/myfm/__init__.py +++ b/src/myfm/__init__.py @@ -1,10 +1,9 @@ from pkg_resources import DistributionNotFound, get_distribution # type: ignore try: - __version__ = get_distribution("myfm").version -except DistributionNotFound: # pragma: no cover - # package is not installed - pass # pragma: no cover + from ._version import __version__ +except: # pragma: no cover + __version__ = "0.0.0" from ._myfm import RelationBlock from .gibbs import MyFMGibbsClassifier, MyFMGibbsRegressor, MyFMOrderedProbit diff --git a/src/myfm/_myfm.pyi b/src/myfm/_myfm.pyi index 51de1d4..9f61d21 100644 --- a/src/myfm/_myfm.pyi +++ b/src/myfm/_myfm.pyi @@ -1,204 +1,25 @@ -"""Backend C++ implementation for myfm.""" -from typing import Iterable as iterable -from typing import Iterator as iterator -from typing import * +import enum +from collections.abc import Callable, Sequence +from typing import Annotated import scipy.sparse -from numpy import float64 -from numpy import typing as npt +from numpy.typing import ArrayLike -import myfm._myfm +class TaskType(enum.Enum): + REGRESSION = 0 -__all__ = [ - "ConfigBuilder", - "FM", - "FMHyperParameters", - "FMLearningConfig", - "FMTrainer", - "LearningHistory", - "Predictor", - "RelationBlock", - "TaskType", - "VariationalFM", - "VariationalFMHyperParameters", - "VariationalFMTrainer", - "VariationalLearningHistory", - "VariationalPredictor", - "create_train_fm", - "create_train_vfm", - "mean_var_truncated_normal_left", - "mean_var_truncated_normal_right", -] + CLASSIFICATION = 1 -class ConfigBuilder: - def __init__(self) -> None: ... - def build(self) -> FMLearningConfig: ... - def set_alpha_0(self, arg0: float) -> ConfigBuilder: ... - def set_beta_0(self, arg0: float) -> ConfigBuilder: ... - def set_cutpoint_groups( - self, arg0: List[Tuple[int, List[int]]] - ) -> ConfigBuilder: ... - def set_cutpoint_scale(self, arg0: float) -> ConfigBuilder: ... - def set_gamma_0(self, arg0: float) -> ConfigBuilder: ... - def set_group_index(self, arg0: List[int]) -> ConfigBuilder: ... - def set_identical_groups(self, arg0: int) -> ConfigBuilder: ... - def set_mu_0(self, arg0: float) -> ConfigBuilder: ... - def set_n_iter(self, arg0: int) -> ConfigBuilder: ... - def set_n_kept_samples(self, arg0: int) -> ConfigBuilder: ... - def set_nu_oprobit(self, arg0: int) -> ConfigBuilder: ... - def set_reg_0(self, arg0: float) -> ConfigBuilder: ... - def set_task_type(self, arg0: TaskType) -> ConfigBuilder: ... - pass - -class FM: - def __getstate__(self) -> tuple: ... - def __repr__(self) -> str: ... - def __setstate__(self, arg0: tuple) -> None: ... - def predict_score( - self, arg0: scipy.sparse.csr_matrix[float64], arg1: List[RelationBlock] - ) -> npt.NDArray[float64]: ... - def oprobit_predict_proba( - self, - arg0: scipy.sparse.csr_matrix[float64], - arg1: List[RelationBlock], - arg2: int, - ) -> npt.NDArray[float64]: ... - @property - def V(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ - @V.setter - def V(self, arg0: npt.NDArray[float64]) -> None: - pass - @property - def cutpoints(self) -> List[npt.NDArray[float64]]: - """ - :type: List[npt.NDArray[float64]] - """ - @cutpoints.setter - def cutpoints(self, arg0: List[npt.NDArray[float64]]) -> None: - pass - @property - def w(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ - @w.setter - def w(self, arg0: npt.NDArray[float64]) -> None: - pass - @property - def w0(self) -> float: - """ - :type: float - """ - @w0.setter - def w0(self, arg0: float) -> None: - pass - pass - -class FMHyperParameters: - def __getstate__(self) -> tuple: ... - def __setstate__(self, arg0: tuple) -> None: ... - @property - def alpha(self) -> float: - """ - :type: float - """ - @property - def lambda_V(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ - @property - def lambda_w(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ - @property - def mu_V(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ - @property - def mu_w(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ - pass + ORDERED = 2 class FMLearningConfig: pass -class FMTrainer: - def __init__( - self, - arg0: scipy.sparse.csr_matrix[float64], - arg1: List[RelationBlock], - arg2: npt.NDArray[float64], - arg3: int, - arg4: FMLearningConfig, - ) -> None: ... - def create_FM(self, arg0: int, arg1: float) -> FM: ... - def create_Hyper(self, arg0: int) -> FMHyperParameters: ... - pass - -class LearningHistory: - def __getstate__(self) -> tuple: ... - def __setstate__(self, arg0: tuple) -> None: ... - @property - def hypers(self) -> List[FMHyperParameters]: - """ - :type: List[FMHyperParameters] - """ - @property - def n_mh_accept(self) -> List[int]: - """ - :type: List[int] - """ - @property - def train_log_losses(self) -> List[float]: - """ - :type: List[float] - """ - pass - -class Predictor: - def __getstate__(self) -> tuple: ... - def __setstate__(self, arg0: tuple) -> None: ... - def predict( - self, arg0: scipy.sparse.csr_matrix[float64], arg1: List[RelationBlock] - ) -> npt.NDArray[float64]: ... - def predict_parallel( - self, - arg0: scipy.sparse.csr_matrix[float64], - arg1: List[RelationBlock], - arg2: int, - ) -> npt.NDArray[float64]: ... - def predict_parallel_oprobit( - self, - arg0: scipy.sparse.csr_matrix[float64], - arg1: List[RelationBlock], - arg2: int, - arg3: int, - ) -> npt.NDArray[float64]: ... - @property - def samples(self) -> List[FM]: - """ - :type: List[FM] - """ - pass - class RelationBlock: - """ - The RelationBlock Class. - """ + """The RelationBlock Class.""" - def __getstate__(self) -> tuple: ... def __init__( - self, - original_to_block: List[int], - data: scipy.sparse.csr_matrix[float64], + self, original_to_block: Sequence[int], data: scipy.sparse.csr_matrix[float] ) -> None: """ Initializes relation block. @@ -215,257 +36,411 @@ class RelationBlock: ----- The entries of `original_to_block` must be in the [0, data.shape[0]-1]. """ - def __repr__(self) -> str: ... - def __setstate__(self, arg0: tuple) -> None: ... + @property - def block_size(self) -> int: - """ - :type: int - """ + def original_to_block(self) -> list[int]: ... @property - def data(self) -> scipy.sparse.csr_matrix[float64]: - """ - :type: scipy.sparse.csr_matrix[float64] - """ + def data(self) -> scipy.sparse.csr_matrix[float]: ... @property - def feature_size(self) -> int: - """ - :type: int - """ + def mapper_size(self) -> int: ... @property - def mapper_size(self) -> int: - """ - :type: int - """ + def block_size(self) -> int: ... @property - def original_to_block(self) -> List[int]: - """ - :type: List[int] - """ - pass - -class TaskType: - """ - Members: - - REGRESSION - - CLASSIFICATION + def feature_size(self) -> int: ... + def __repr__(self) -> str: ... + def __getstate__(self) -> tuple[list[int], scipy.sparse.csr_matrix[float]]: ... + def __setstate__( + self, arg: tuple[Sequence[int], scipy.sparse.csr_matrix[float]], / + ) -> None: ... - ORDERED - """ +class ConfigBuilder: + def __init__(self) -> None: ... + def set_alpha_0(self, arg: float, /) -> None: ... + def set_beta_0(self, arg: float, /) -> None: ... + def set_gamma_0(self, arg: float, /) -> None: ... + def set_mu_0(self, arg: float, /) -> None: ... + def set_reg_0(self, arg: float, /) -> None: ... + def set_n_iter(self, arg: int, /) -> None: ... + def set_n_kept_samples(self, arg: int, /) -> None: ... + def set_task_type(self, arg: TaskType, /) -> None: ... + def set_nu_oprobit(self, arg: int, /) -> None: ... + def set_fit_w0(self, arg: bool, /) -> None: ... + def set_fit_linear(self, arg: bool, /) -> None: ... + def set_group_index(self, arg: Sequence[int], /) -> None: ... + def set_identical_groups(self, arg: int, /) -> None: ... + def set_cutpoint_scale(self, arg: float, /) -> None: ... + def set_cutpoint_groups( + self, arg: Sequence[tuple[int, Sequence[int]]], / + ) -> None: ... + def build(self) -> FMLearningConfig: ... - def __init__(self, arg0: int) -> None: ... - def __int__(self) -> int: ... +class FM: @property - def name(self) -> str: - """ - (self: handle) -> str - - :type: str - """ - CLASSIFICATION: myfm._myfm.TaskType # value = TaskType.CLASSIFICATION - ORDERED: myfm._myfm.TaskType # value = TaskType.ORDERED - REGRESSION: myfm._myfm.TaskType # value = TaskType.REGRESSION - __entries: dict # value = {'REGRESSION': (TaskType.REGRESSION, None), 'CLASSIFICATION': (TaskType.CLASSIFICATION, None), 'ORDERED': (TaskType.ORDERED, None)} - __members__: dict # value = {'REGRESSION': TaskType.REGRESSION, 'CLASSIFICATION': TaskType.CLASSIFICATION, 'ORDERED': TaskType.ORDERED} - pass + def w0(self) -> float: ... + @w0.setter + def w0(self, arg: float, /) -> None: ... + @property + def w( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... + @w.setter + def w( + self, + arg: Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + /, + ) -> None: ... + @property + def V( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... + @V.setter + def V( + self, + arg: Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + /, + ) -> None: ... + @property + def cutpoints( + self, + ) -> list[Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]]: ... + @cutpoints.setter + def cutpoints( + self, + arg: Sequence[ + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")] + ], + /, + ) -> None: ... + def predict_score( + self, arg0: scipy.sparse.csr_matrix[float], arg1: Sequence[RelationBlock], / + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... + def oprobit_predict_proba( + self, + arg0: scipy.sparse.csr_matrix[float], + arg1: Sequence[RelationBlock], + arg2: int, + /, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... + def __repr__(self) -> str: ... + def __getstate__( + self, + ) -> tuple[ + float, + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + list[Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]], + ]: ... + def __setstate__( + self, + arg: tuple[ + float, + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + Sequence[ + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")] + ], + ], + /, + ) -> None: ... class VariationalFM: - def __getstate__(self) -> tuple: ... - def __repr__(self) -> str: ... - def __setstate__(self, arg0: tuple) -> None: ... - def predict_score( - self, arg0: scipy.sparse.csr_matrix[float64], arg1: List[RelationBlock] - ) -> npt.NDArray[float64]: ... @property - def V(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ + def w0(self) -> float: ... + @w0.setter + def w0(self, arg: float, /) -> None: ... + @property + def w0_var(self) -> float: ... + @w0_var.setter + def w0_var(self, arg: float, /) -> None: ... + @property + def w( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... + @w.setter + def w( + self, + arg: Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + /, + ) -> None: ... + @property + def w_var( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... + @w_var.setter + def w_var( + self, + arg: Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + /, + ) -> None: ... + @property + def V( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... @V.setter - def V(self, arg0: npt.NDArray[float64]) -> None: - pass + def V( + self, + arg: Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + /, + ) -> None: ... @property - def V_var(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ + def V_var( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... @V_var.setter - def V_var(self, arg0: npt.NDArray[float64]) -> None: - pass + def V_var( + self, + arg: Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + /, + ) -> None: ... @property - def cutpoints(self) -> List[npt.NDArray[float64]]: - """ - :type: List[npt.NDArray[float64]] - """ + def cutpoints( + self, + ) -> list[Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]]: ... @cutpoints.setter - def cutpoints(self, arg0: List[npt.NDArray[float64]]) -> None: - pass + def cutpoints( + self, + arg: Sequence[ + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")] + ], + /, + ) -> None: ... + def predict_score( + self, arg0: scipy.sparse.csr_matrix[float], arg1: Sequence[RelationBlock], / + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... + def __repr__(self) -> str: ... + def __getstate__( + self, + ) -> tuple[ + float, + float, + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + list[Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]], + ]: ... + def __setstate__( + self, + arg: tuple[ + float, + float, + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + Sequence[ + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")] + ], + ], + /, + ) -> None: ... + +class FMHyperParameters: @property - def w(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ - @w.setter - def w(self, arg0: npt.NDArray[float64]) -> None: - pass + def alpha(self) -> float: ... @property - def w0(self) -> float: - """ - :type: float - """ - @w0.setter - def w0(self, arg0: float) -> None: - pass + def mu_w( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... @property - def w0_var(self) -> float: - """ - :type: float - """ - @w0_var.setter - def w0_var(self, arg0: float) -> None: - pass + def lambda_w( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... @property - def w_var(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ - @w_var.setter - def w_var(self, arg0: npt.NDArray[float64]) -> None: - pass - pass + def mu_V( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... + @property + def lambda_V( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... + def __getstate__( + self, + ) -> tuple[ + float, + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + ]: ... + def __setstate__( + self, + arg: tuple[ + float, + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + ], + /, + ) -> None: ... class VariationalFMHyperParameters: - def __getstate__(self) -> tuple: ... - def __setstate__(self, arg0: tuple) -> None: ... @property - def alpha(self) -> float: - """ - :type: float - """ + def alpha(self) -> float: ... @property - def alpha_rate(self) -> float: - """ - :type: float - """ + def alpha_rate(self) -> float: ... @property - def lambda_V(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ + def mu_w( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... @property - def lambda_V_rate(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ + def mu_w_var( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... @property - def lambda_w(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ + def lambda_w( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... @property - def lambda_w_rate(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ + def lambda_w_rate( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... @property - def mu_V(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ + def mu_V( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... @property - def mu_V_var(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ + def mu_V_var( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... @property - def mu_w(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ + def lambda_V( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... @property - def mu_w_var(self) -> npt.NDArray[float64]: - """ - :type: npt.NDArray[float64] - """ - pass + def lambda_V_rate( + self, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... + def __getstate__(self) -> tuple: ... + def __setstate__( + self, + arg: tuple[ + float, + float, + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")], + ], + /, + ) -> None: ... + +class Predictor: + @property + def samples(self) -> list[FM]: ... + def predict( + self, arg0: scipy.sparse.csr_matrix[float], arg1: Sequence[RelationBlock], / + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... + def predict_parallel( + self, + arg0: scipy.sparse.csr_matrix[float], + arg1: Sequence[RelationBlock], + arg2: int, + /, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... + def predict_parallel_oprobit( + self, + arg0: scipy.sparse.csr_matrix[float], + arg1: Sequence[RelationBlock], + arg2: int, + arg3: int, + /, + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None, None), order="F")]: ... + def __getstate__(self) -> tuple[int, int, int, list[FM]]: ... + def __setstate__(self, arg: tuple[int, int, TaskType, Sequence[FM]], /) -> None: ... + +class VariationalPredictor: + def predict( + self, arg0: scipy.sparse.csr_matrix[float], arg1: Sequence[RelationBlock], / + ) -> Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")]: ... + def weights(self) -> VariationalFM: ... + def __getstate__(self) -> tuple[int, int, int, list[VariationalFM]]: ... + def __setstate__( + self, arg: tuple[int, int, TaskType, Sequence[VariationalFM]], / + ) -> None: ... + +class FMTrainer: + def __init__( + self, + arg0: scipy.sparse.csr_matrix[float], + arg1: Sequence[RelationBlock], + arg2: Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], + arg3: int, + arg4: FMLearningConfig, + /, + ) -> None: ... + def create_FM(self, arg0: int, arg1: float, /) -> FM: ... + def create_Hyper(self, arg: int, /) -> FMHyperParameters: ... class VariationalFMTrainer: def __init__( self, - arg0: scipy.sparse.csr_matrix[float64], - arg1: List[RelationBlock], - arg2: npt.NDArray[float64], + arg0: scipy.sparse.csr_matrix[float], + arg1: Sequence[RelationBlock], + arg2: Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], arg3: int, arg4: FMLearningConfig, + /, ) -> None: ... - def create_FM(self, arg0: int, arg1: float) -> VariationalFM: ... - def create_Hyper(self, arg0: int) -> VariationalFMHyperParameters: ... - pass + def create_FM(self, arg0: int, arg1: float, /) -> VariationalFM: ... + def create_Hyper(self, arg: int, /) -> VariationalFMHyperParameters: ... -class VariationalLearningHistory: - def __getstate__(self) -> tuple: ... - def __setstate__(self, arg0: tuple) -> None: ... +class LearningHistory: @property - def elbos(self) -> List[float]: - """ - :type: List[float] - """ + def hypers(self) -> list[FMHyperParameters]: ... @property - def hypers(self) -> FMHyperParameters: - """ - :type: FMHyperParameters - """ - pass + def train_log_losses(self) -> list[float]: ... + @property + def n_mh_accept(self) -> list[int]: ... + def __getstate__( + self, + ) -> tuple[list[FMHyperParameters], list[float], list[int]]: ... + def __setstate__( + self, arg: tuple[Sequence[FMHyperParameters], Sequence[float], Sequence[int]], / + ) -> None: ... -class VariationalPredictor: - def __getstate__(self) -> tuple: ... - def __setstate__(self, arg0: tuple) -> None: ... - def predict( - self, arg0: scipy.sparse.csr_matrix[float64], arg1: List[RelationBlock] - ) -> npt.NDArray[float64]: ... - def weights(self) -> VariationalFM: ... - pass +class VariationalLearningHistory: + @property + def hypers(self) -> FMHyperParameters: ... + @property + def elbos(self) -> list[float]: ... + def __getstate__(self) -> tuple[FMHyperParameters, list[float]]: ... + def __setstate__( + self, arg: tuple[FMHyperParameters, Sequence[float]], / + ) -> None: ... def create_train_fm( arg0: int, arg1: float, - arg2: scipy.sparse.csr_matrix[float64], - arg3: List[RelationBlock], - arg4: npt.NDArray[float64], + arg2: scipy.sparse.csr_matrix[float], + arg3: Sequence[RelationBlock], + arg4: Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], arg5: int, arg6: FMLearningConfig, arg7: Callable[[int, FM, FMHyperParameters, LearningHistory], bool], -) -> Tuple[Predictor, LearningHistory]: - """ - create and train fm. - """ + /, +) -> tuple[Predictor, LearningHistory]: + """create and train fm.""" def create_train_vfm( rank: int, init_std: float, - X: scipy.sparse.csr_matrix[float64], - relations: List[RelationBlock], - y: npt.NDArray[float64], + X: scipy.sparse.csr_matrix[float], + relations: Sequence[RelationBlock], + y: Annotated[ArrayLike, dict(dtype="float64", shape=(None), order="C")], random_seed: int, learning_config: FMLearningConfig, callback: Callable[ - [ - int, - VariationalFM, - VariationalFMHyperParameters, - VariationalLearningHistory, - ], + [int, VariationalFM, VariationalFMHyperParameters, VariationalLearningHistory], bool, ], -) -> Tuple[VariationalPredictor, VariationalLearningHistory]: - """ - create and train fm. - """ - -def mean_var_truncated_normal_left(arg0: float) -> Tuple[float, float, float]: - pass +) -> tuple[VariationalPredictor, VariationalLearningHistory]: + """create and train fm.""" -def mean_var_truncated_normal_right(arg0: float) -> Tuple[float, float, float]: - pass +def mean_var_truncated_normal_left(arg: float, /) -> tuple[float, float, float]: ... +def mean_var_truncated_normal_right(arg: float, /) -> tuple[float, float, float]: ... diff --git a/src/myfm/base.py b/src/myfm/base.py index 2b78f86..02abc84 100644 --- a/src/myfm/base.py +++ b/src/myfm/base.py @@ -280,7 +280,8 @@ def _fit( else: do_test = False - config_builder.set_n_iter(n_iter).set_n_kept_samples(n_kept_samples) + config_builder.set_n_iter(n_iter) + config_builder.set_n_kept_samples(n_kept_samples) if X.dtype != np.float64: X = X.astype(np.float64) diff --git a/src/myfm/gibbs.py b/src/myfm/gibbs.py index 1dee991..ca0e37a 100644 --- a/src/myfm/gibbs.py +++ b/src/myfm/gibbs.py @@ -46,7 +46,7 @@ def w0_samples(self) -> Optional[DenseArray]: """ if self.predictor_ is None: return None - return np.asfarray([fm.w0 for fm in self.predictor_.samples]) + return np.asarray([fm.w0 for fm in self.predictor_.samples]) @property def w_samples(self) -> Optional[DenseArray]: @@ -58,7 +58,7 @@ def w_samples(self) -> Optional[DenseArray]: """ if self.predictor_ is None: return None - return np.asfarray([fm.w for fm in self.predictor_.samples]) + return np.asarray([fm.w for fm in self.predictor_.samples]) @property def V_samples(self) -> Optional[DenseArray]: @@ -70,7 +70,7 @@ def V_samples(self) -> Optional[DenseArray]: """ if self.predictor_ is None: return None - return np.asfarray([fm.V for fm in self.predictor_.samples]) + return np.asarray([fm.V for fm in self.predictor_.samples]) def _predict_core( self, @@ -540,4 +540,4 @@ def cutpoint_samples(self) -> Optional[DenseArray]: """ if self.predictor_ is None: return None - return np.asfarray([fm.cutpoints[0] for fm in self.predictor_.samples]) + return np.asarray([fm.cutpoints[0] for fm in self.predictor_.samples]) diff --git a/src/myfm/utils/dummy_data.py b/src/myfm/utils/dummy_data.py index f2ef1e5..27720b8 100644 --- a/src/myfm/utils/dummy_data.py +++ b/src/myfm/utils/dummy_data.py @@ -39,7 +39,7 @@ def gen_dummy_rating_df( user_factor[result_df[user_colname].values - 1, :] * item_factor[result_df[item_colname].values - 1, :] ).sum(axis=1) - cutpoints: List[float] = list(np.percentile(score, [20, 40, 60, 80])) # type: ignore + cutpoints: List[float] = list(np.percentile(score, [20, 40, 60, 80])) rating = np.ones((size,), dtype=np.int64) for cp in cutpoints: rating += score >= cp diff --git a/src/myfm/utils/encoders/binning.py b/src/myfm/utils/encoders/binning.py index 52445ae..5cadebe 100644 --- a/src/myfm/utils/encoders/binning.py +++ b/src/myfm/utils/encoders/binning.py @@ -32,7 +32,7 @@ def __init__(self, x: ArrayLike, n_percentiles: int = 10) -> None: if n_percentiles <= 0: raise ValueError("n_percentiles must be greater than 0.") self.percentages = np.linspace(0, 100, n_percentiles + 2)[1:-1] - x_arr = np.asfarray(x) + x_arr = np.asarray(x) temp_percentiles: DenseArray = np.percentile( x_arr[~np.isnan(x_arr)], self.percentages ) diff --git a/tests/classification/test_classification.py b/tests/classification/test_classification.py index 3240227..b69e7db 100644 --- a/tests/classification/test_classification.py +++ b/tests/classification/test_classification.py @@ -1,3 +1,5 @@ +import pickle +from io import BytesIO from typing import Tuple import numpy as np @@ -32,6 +34,12 @@ def test_middle_clf( if use_libfm_callback: np.testing.assert_allclose(fm.predict_proba(X), callback.predictions / 200) + stream = BytesIO() + pickle.dump(fm, stream) + stream.seek(0) + read_fm = pickle.load(stream) + np.testing.assert_allclose(fm.predict_proba(X), read_fm.predict_proba(X)) + vfm_before_fit = VariationalFMClassifier(3) assert vfm_before_fit.w0_mean is None assert vfm_before_fit.w0_var is None @@ -53,6 +61,14 @@ def test_middle_clf( assert fm.predictor_ is not None + vfm_stream = BytesIO() + pickle.dump(vfm, vfm_stream) + vfm_stream.seek(0) + read_vfm = pickle.load(vfm_stream) + np.testing.assert_allclose(vfm.predict_proba(X), read_vfm.predict_proba(X)) + + vfm.predict_proba(X) + last_samples = fm.predictor_.samples[-20:] for i in range(3): diff --git a/tests/conftest.py b/tests/conftest.py index 3e003e8..6a213a6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,10 +14,8 @@ def stub_weight() -> FMWeights: weights = FMWeights( -3.0, - np.asfarray([1.0, 2.0, -1.0]), - np.asfarray( - [[1.0, -1.0, 0], [0.0, 1.0, 1.0], [1.0, 1.0, 1.0], [-1.0, 0, -1.0]] - ), + np.asarray([1.0, 2.0, -1.0]), + np.asarray([[1.0, -1.0, 0], [0.0, 1.0, 1.0], [1.0, 1.0, 1.0], [-1.0, 0, -1.0]]), ) return weights diff --git a/tests/oprobit/test_oprobit_1dim.py b/tests/oprobit/test_oprobit_1dim.py index 6d655f5..0618538 100644 --- a/tests/oprobit/test_oprobit_1dim.py +++ b/tests/oprobit/test_oprobit_1dim.py @@ -9,7 +9,7 @@ @pytest.mark.parametrize("use_libfm_callback", [True, False]) def test_oprobit(use_libfm_callback: bool) -> None: N_train = 1000 - cps = np.asfarray([0.0, 0.5, 1.5]) + cps = np.asarray([0.0, 0.5, 1.5]) rns = np.random.RandomState(0) X = rns.normal(0, 2, size=N_train) coeff = 0.5 diff --git a/tests/utils/test_categorical.py b/tests/utils/test_categorical.py index 244bcfa..3504a12 100644 --- a/tests/utils/test_categorical.py +++ b/tests/utils/test_categorical.py @@ -31,7 +31,7 @@ def test_categorical_encs_create() -> None: assert len(enc_cutoff) == 3 X_cutoffed = enc_cutoff.to_sparse(["item4", "item1", "item2", "item3"]) np.testing.assert_allclose( - X_cutoffed.toarray(), np.asfarray([[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 0, 0]]) + X_cutoffed.toarray(), np.asarray([[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 0, 0]]) ) @@ -39,14 +39,14 @@ def test_categorical_encs_ignore() -> None: enc = CategoryValueToSparseEncoder(TEST_ITEMS, handle_unknown="ignore") X = enc.to_sparse(["item4", "item1", "item2", "item3"]) np.testing.assert_allclose( - X.toarray(), np.asfarray([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]]) + X.toarray(), np.asarray([[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1]]) ) enc_cutoff = CategoryValueToSparseEncoder( TEST_ITEMS, handle_unknown="ignore", min_freq=3 ) X = enc_cutoff.to_sparse(["item4", "item1", "item2", "item3"]) np.testing.assert_allclose( - X.toarray(), np.asfarray([[0, 0], [1, 0], [0, 1], [0, 0]]) + X.toarray(), np.asarray([[0, 0], [1, 0], [0, 1], [0, 0]]) ) @@ -57,5 +57,5 @@ def test_categorical_encs_raise() -> None: X = enc.to_sparse(["item1", "item2", "item3"]) np.testing.assert_allclose( - X.toarray(), np.asfarray([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + X.toarray(), np.asarray([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) )