diff --git a/.github/workflows/doctest.yml b/.github/workflows/doctest.yml index 19e14c6..7f26d13 100644 --- a/.github/workflows/doctest.yml +++ b/.github/workflows/doctest.yml @@ -1,7 +1,7 @@ name: Doctest on: [push] jobs: - run_pytest_upload_coverage: + test_readme_and_sphinx_docs: runs-on: ubuntu-latest env: OS: ubuntu-latest @@ -17,7 +17,7 @@ jobs: run: | pip install --upgrade pip pip install numpy scipy pandas scikit-learn - python setup.py install + pip install . curl http://files.grouplens.org/datasets/movielens/ml-100k.zip -o ~/.ml-100k.zip - name: Run pytest run: | diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 96bb78b..dbe926b 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -10,4 +10,4 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.0 + - uses: pre-commit/action@v2.0.3 diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index fa8284f..223ffa1 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -18,7 +18,7 @@ jobs: pip install --upgrade pip pip install numpy scipy pandas sudo apt-get install lcov - TEST_BUILD=true python setup.py develop + CXXFLAGS="-O0 -g -coverage" pip install -e . - name: Run pytest run: | pip install pytest pytest-cov pytest-mock diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 976118a..1011b8d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -19,9 +19,9 @@ jobs: - uses: actions/setup-python@v2 name: Install Python with: - python-version: '3.7' + python-version: "3.7" - name: Build sdist - run: python setup.py sdist + run: pip install pybind11 && python setup.py sdist - uses: actions/upload-artifact@v2 with: path: dist/*.tar.gz @@ -126,7 +126,6 @@ jobs: - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse - - uses: actions/upload-artifact@v2 with: path: ./wheelhouse/*.whl diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca78bc2..58c9c20 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: - id: isort name: isort - repo: https://github.com/psf/black - rev: 20.8b1 # Replace by any tag/version: https://github.com/psf/black/tags + rev: 22.3.0 hooks: - id: black language_version: python3 # Should be a command that runs python3.6+ diff --git a/pyproject.toml b/pyproject.toml index 2d85b90..0b20321 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,14 @@ +[build-system] +requires = [ + "setuptools>=42", + "wheel", + "pybind11>=2.8.0", + "requests", + "setuptools_scm[toml]>=6.2", +] + +build-backend = "setuptools.build_meta" + [tool.black] ensure_newline_before_comments = true force_grid_wrap = 0 @@ -10,6 +21,7 @@ use_parentheses = true ensure_newline_before_comments = true force_grid_wrap = 0 include_trailing_comma = true +known_third_party = ["pybind11"] line_length = 88 multi_line_output = 3 use_parentheses = true diff --git a/setup.py b/setup.py index a300cd4..3622480 100644 --- a/setup.py +++ b/setup.py @@ -1,16 +1,9 @@ -# Taken from -# https://github.com/wichert/pybind11-example/blob/master/setup.py -# and modified. - import os -import sys -from distutils.ccompiler import CCompiler from pathlib import Path -from typing import Any, Dict, List +from typing import Any -import setuptools -from setuptools import Extension, find_packages, setup -from setuptools.command.build_ext import build_ext +from pybind11.setup_helpers import Pybind11Extension, build_ext +from setuptools import find_packages, setup install_requires = [ "numpy>=1.11", @@ -19,10 +12,7 @@ "pandas>=1.0.0", "typing-extensions>=4.0.0", ] -setup_requires = ["pybind11>=2.5", "requests", "setuptools_scm"] - -eigen_include_dir = os.environ.get("EIGEN3_INCLUDE_DIR", None) TEST_BUILD = os.environ.get("TEST_BUILD", None) is not None @@ -32,7 +22,7 @@ class get_eigen_include(object): 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 @@ -59,22 +49,6 @@ def __str__(self) -> str: return str(target_dir) -class get_pybind_include: - """Helper class to determine the pybind11 include path - The purpose of this class is to postpone importing pybind11 - until it is actually installed, so that the ``get_include()`` - method can be invoked.""" - - def __init__(self, user: bool = False): - self.user = user - - def __str__(self) -> str: - import pybind11 - - include_dir: str = pybind11.get_include(self.user) - return include_dir - - headers = [ "include/myfm/definitions.hpp", "include/myfm/util.hpp", @@ -90,93 +64,18 @@ def __str__(self) -> str: ext_modules = [ - Extension( + Pybind11Extension( "myfm._myfm", ["cpp_source/bind.cpp", "cpp_source/Faddeeva.cc"], include_dirs=[ # Path to pybind11 headers - get_pybind_include(), - get_pybind_include(user=True), get_eigen_include(), "include", ], - language="c++", ), ] -def has_flag(compiler: CCompiler, flagname: str) -> bool: - """Return a boolean indicating whether a flag name is supported on - the specified compiler. - """ - import tempfile - - with tempfile.NamedTemporaryFile("w", suffix=".cpp") as f: - f.write("int main (int argc, char **argv) { return 0; }") - try: - compiler.compile([f.name], extra_postargs=[flagname]) - except setuptools.distutils.errors.CompileError: - return False - return True - - -def cpp_flag(compiler: CCompiler) -> str: - """Return the -std=c++[11/14/17] compiler flag. - The newer version is prefered over c++11 (when it is available). - """ - flags = ["-std=c++11"] - - for flag in flags: - if has_flag(compiler, flag): - return flag - - raise RuntimeError("Unsupported compiler -- at least C++11 support is needed!") - - -class BuildExt(build_ext): - """A custom build extension for adding compiler-specific options.""" - - if TEST_BUILD: - c_opts: Dict[str, List[str]] = { - "msvc": ["/EHsc"], - "unix": ["-O0", "-coverage", "-g"], - } - l_opts: Dict[str, List[str]] = { - "msvc": [], - "unix": ["-coverage"], - } - else: - c_opts = { - "msvc": ["/EHsc"], - "unix": [], - } - l_opts = { - "msvc": [], - "unix": [], - } - - if sys.platform == "darwin": - darwin_opts = ["-stdlib=libc++", "-mmacosx-version-min=10.7"] - c_opts["unix"] += darwin_opts - l_opts["unix"] += darwin_opts - - def build_extensions(self) -> None: - ct = self.compiler.compiler_type - opts = self.c_opts.get(ct, []) - link_opts = self.l_opts.get(ct, []) - if ct == "unix": - opts.append('-DVERSION_INFO="%s"' % self.distribution.get_version()) - opts.append(cpp_flag(self.compiler)) - if has_flag(self.compiler, "-fvisibility=hidden"): - opts.append("-fvisibility=hidden") - elif ct == "msvc": - opts.append('/DVERSION_INFO=\\"%s\\"' % self.distribution.get_version()) - for ext in self.extensions: - ext.extra_compile_args = opts - ext.extra_link_args = link_opts - build_ext.build_extensions(self) - - def local_scheme(version: Any) -> str: return "" @@ -186,17 +85,16 @@ def local_scheme(version: Any) -> str: use_scm_version={"local_scheme": local_scheme}, author="Tomoki Ohtsuki", url="https://github.com/tohtsky/myfm", - author_email="tomoki.ohtsuki129@gmail.com", - description="Yet another factorization machine", + author_email="tomoki.ohtsuki.19937@outlook.jp", + description="Yet another Bayesian factorization machines.", long_description="", ext_modules=ext_modules, install_requires=install_requires, - setup_requires=setup_requires, - cmdclass={"build_ext": BuildExt}, - packages=find_packages("src"), + cmdclass={"build_ext": build_ext}, package_dir={"": "src"}, zip_safe=False, headers=headers, - python_requires=">=3.6.0", + python_requires=">=3.6", + packages=find_packages("src"), package_data={"myfm": ["*.pyi"]}, ) diff --git a/src/myfm/utils/dummy_data.py b/src/myfm/utils/dummy_data.py index 79a004e..f2ef1e5 100644 --- a/src/myfm/utils/dummy_data.py +++ b/src/myfm/utils/dummy_data.py @@ -20,7 +20,7 @@ def gen_dummy_rating_df( user_indices_all = np.arange(max(int(size / 3), 10)) item_indices_all = np.arange(max(int(size / 2), 10)) user_factor = rns.normal( - 0, 1 / factor_rank ** 0.5, size=(user_indices_all.shape[0], factor_rank) + 0, 1 / factor_rank**0.5, size=(user_indices_all.shape[0], factor_rank) ) item_factor = rns.normal(0, 1, size=(item_indices_all.shape[0], factor_rank)) diff --git a/tests/regression/test_block.py b/tests/regression/test_block.py index 8c8d6fc..e1bf8a0 100644 --- a/tests/regression/test_block.py +++ b/tests/regression/test_block.py @@ -29,9 +29,9 @@ def test_block_vfm() -> None: [tm_column, user_block[user_indices], item_block[item_indices]] ) X_flatten_squread = X_flatten.copy() - X_flatten_squread.data = X_flatten_squread.data ** 2 + X_flatten_squread.data = X_flatten_squread.data**2 factor = rns.randn(X_flatten.shape[1], 3) - f2 = (factor ** 2).sum(axis=1) + f2 = (factor**2).sum(axis=1) Xf = X_flatten.dot(factor) gb = 3.0 @@ -39,7 +39,7 @@ def test_block_vfm() -> None: y = ( gb + X_flatten.dot(linear_weights) - + 0.5 * ((Xf ** 2).sum(axis=1) - X_flatten_squread.dot(f2)) + + 0.5 * ((Xf**2).sum(axis=1) - X_flatten_squread.dot(f2)) + rns.normal(1.0, size=X_flatten.shape[0]) ) @@ -111,12 +111,12 @@ def test_block() -> None: [tm_column, user_block[user_indices], item_block[item_indices]] ) X_flatten_squread = X_flatten.copy() - X_flatten_squread.data = X_flatten_squread.data ** 2 + X_flatten_squread.data = X_flatten_squread.data**2 weights = rns.randn(3, X_flatten.shape[1]) Xw = X_flatten.dot(weights.T) - X2w2 = X_flatten_squread.dot((weights ** 2).sum(axis=0)) - y = 0.5 * ((Xw ** 2).sum(axis=1) - X2w2) + rns.randn(N_train) + X2w2 = X_flatten_squread.dot((weights**2).sum(axis=0)) + y = 0.5 * ((Xw**2).sum(axis=1) - X2w2) + rns.randn(N_train) blocks = [ RelationBlock(user_indices, user_block), diff --git a/tests/regression/test_fit.py b/tests/regression/test_fit.py index 0349204..c6631f3 100644 --- a/tests/regression/test_fit.py +++ b/tests/regression/test_fit.py @@ -31,8 +31,8 @@ def test_middle_reg( vfm_weights = vfm.predictor_.weights() hp_trance = fm.get_hyper_trace() last_alphs = hp_trance["alpha"].iloc[-20:].values - assert np.all(last_alphs > ((1 / alpha_inv ** 2) / 2)) - assert np.all(last_alphs < ((1 / alpha_inv ** 2) * 2)) + assert np.all(last_alphs > ((1 / alpha_inv**2) / 2)) + assert np.all(last_alphs < ((1 / alpha_inv**2) * 2)) last_samples = fm.predictor_.samples[-20:] assert np.all([s.w0 < stub_weight.global_bias + 0.5 for s in last_samples]) diff --git a/tests/test_utils.py b/tests/test_utils.py index deb0d56..a071241 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -15,11 +15,11 @@ class FMWeights(NamedTuple): def prediction(X: sps.csr_matrix, weight: FMWeights) -> np.ndarray: X2 = X.copy() - X2.data[:] = X2.data ** 2 + X2.data[:] = X2.data**2 result = np.zeros(X.shape[0], dtype=np.float64) result[:] = weight.global_bias result += X.dot(weight.weight) - w2 = (weight.factors ** 2).sum(axis=0) + w2 = (weight.factors**2).sum(axis=0) Xw = X.dot(weight.factors.T) - result += ((Xw ** 2).sum(axis=1) - (X2.dot(w2))) * 0.5 + result += ((Xw**2).sum(axis=1) - (X2.dot(w2))) * 0.5 return result