diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..cf51ba2 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,18 @@ +FROM mcr.microsoft.com/devcontainers/cpp:1-debian-11 + +ARG REINSTALL_CMAKE_VERSION_FROM_SOURCE="none" + +# Optionally install the cmake for vcpkg +COPY ./reinstall-cmake.sh /tmp/ + +RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \ + chmod +x /tmp/reinstall-cmake.sh && /tmp/reinstall-cmake.sh ${REINSTALL_CMAKE_VERSION_FROM_SOURCE}; \ + fi \ + && rm -f /tmp/reinstall-cmake.sh + +# [Optional] Uncomment this section to install additional vcpkg ports. +# RUN su vscode -c "${VCPKG_ROOT}/vcpkg install " + +# [Optional] Uncomment this section to install additional packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..a75ac7c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,26 @@ +// For format details, see https://aka.ms/devcontainer.json. +{ + "name": "C++", + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/devcontainers/features/python:1": {}, + "ghcr.io/devcontainers-contrib/features/meson-asdf:2": {} + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "gcc -v", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.devcontainer/reinstall-cmake.sh b/.devcontainer/reinstall-cmake.sh new file mode 100644 index 0000000..408b81d --- /dev/null +++ b/.devcontainer/reinstall-cmake.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +set -e + +CMAKE_VERSION=${1:-"none"} + +if [ "${CMAKE_VERSION}" = "none" ]; then + echo "No CMake version specified, skipping CMake reinstallation" + exit 0 +fi + +# Cleanup temporary directory and associated files when exiting the script. +cleanup() { + EXIT_CODE=$? + set +e + if [[ -n "${TMP_DIR}" ]]; then + echo "Executing cleanup of tmp files" + rm -Rf "${TMP_DIR}" + fi + exit $EXIT_CODE +} +trap cleanup EXIT + + +echo "Installing CMake..." +apt-get -y purge --auto-remove cmake +mkdir -p /opt/cmake + +architecture=$(dpkg --print-architecture) +case "${architecture}" in + arm64) + ARCH=aarch64 ;; + amd64) + ARCH=x86_64 ;; + *) + echo "Unsupported architecture ${architecture}." + exit 1 + ;; +esac + +CMAKE_BINARY_NAME="cmake-${CMAKE_VERSION}-linux-${ARCH}.sh" +CMAKE_CHECKSUM_NAME="cmake-${CMAKE_VERSION}-SHA-256.txt" +TMP_DIR=$(mktemp -d -t cmake-XXXXXXXXXX) + +echo "${TMP_DIR}" +cd "${TMP_DIR}" + +curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_BINARY_NAME}" -O +curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_CHECKSUM_NAME}" -O + +sha256sum -c --ignore-missing "${CMAKE_CHECKSUM_NAME}" +sh "${TMP_DIR}/${CMAKE_BINARY_NAME}" --prefix=/opt/cmake --skip-license + +ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake +ln -s /opt/cmake/bin/ctest /usr/local/bin/ctest diff --git a/.github/workflows/build_macos.yml b/.github/workflows/build_macos.yml index eb83e3d..1316b71 100644 --- a/.github/workflows/build_macos.yml +++ b/.github/workflows/build_macos.yml @@ -29,7 +29,10 @@ jobs: export LDFLAGS="-L/usr/local/opt/llvm/lib" export CPPFLAGS="-I/usr/local/opt/llvm/include" echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.bash_profile + echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> /Users/runner/.bash_profile source ~/.bash_profile + - name: Verify Clang version + run: | clang --version - name: Install OpenBLAS run: | @@ -48,6 +51,9 @@ jobs: - name: Install package run: | pip install --verbose . + env: + CC: /usr/local/opt/llvm/bin/clang + CXX: /usr/local/opt/llvm/bin/clang - name: Test with pytest run: | python -m pytest tests/ --cov=primate --benchmark-skip diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6c9ab52 --- /dev/null +++ b/Dockerfile @@ -0,0 +1 @@ +FROM cibuildwheel/manylinux1_x86_64:latest \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 7768738..eb2f17c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ requires = ['meson-python', 'wheel', 'ninja', 'pybind11', 'numpy', 'pythran-open [project] name = "primate" -version = '0.2.2' +version = "0.2.3" readme = "README.md" classifiers = [ "Intended Audience :: Science/Research", @@ -18,7 +18,7 @@ authors = [ requires-python = ">=3.8" dependencies = [ "numpy", - "scipy >= 1.11.3", + "scipy", "more_itertools" ] license = {file = "LICENSE"} diff --git a/src/primate/_lanczos.cpp b/src/primate/_lanczos.cpp index 135b615..810f348 100644 --- a/src/primate/_lanczos.cpp +++ b/src/primate/_lanczos.cpp @@ -39,6 +39,8 @@ using py_array = py::array_t< F, py::array::f_style | py::array::forcecast >; template< std::floating_point F > auto param_spectral_func(const py::kwargs& kwargs) -> std::function< F(F) >{ + constexpr F SQRT2_REC = 0.70710678118654752440; // 1 / sqrt(2) + constexpr F SQRT2_RECPI = 1.12837916709551257390; // 2 / sqrt(pi) auto kwargs_map = kwargs.cast< std::unordered_map< std::string, py::object > >(); std::function< F(F) > f = std::identity(); if (kwargs_map.contains("function")){ @@ -68,7 +70,7 @@ auto param_spectral_func(const py::kwargs& kwargs) -> std::function< F(F) >{ F sigma = kwargs_map.contains("sigma") ? kwargs_map["sigma"].cast< F >() : 1.0; f = [mu, sigma](F eigenvalue) -> F { auto x = (mu - eigenvalue) / sigma; - return (0.5 * M_SQRT1_2 * M_2_SQRTPI / sigma) * exp(-0.5 * x * x); + return (0.5 * SQRT2_REC * SQRT2_RECPI / sigma) * exp(-0.5 * x * x); }; } else if (matrix_func == "numrank"){ F threshold = kwargs_map.contains("threshold") ? kwargs_map["threshold"].cast< F >() : 0.000001; diff --git a/src/primate/trace.py b/src/primate/trace.py index 682fd92..9a555a6 100644 --- a/src/primate/trace.py +++ b/src/primate/trace.py @@ -1,6 +1,6 @@ from typing import * import numpy as np -from scipy.sparse import issparse, sparray +from scipy.sparse import issparse from scipy.sparse.linalg import LinearOperator ## Package imports @@ -11,7 +11,7 @@ _builtin_matrix_functions = ["identity", "sqrt", "exp", "pow", "log", "numrank", "smoothstep", "gaussian"] def sl_trace ( - A: Union[LinearOperator, sparray, np.ndarray], + A: Union[LinearOperator, np.ndarray], fun: Union[str, Callable] = "identity", maxiter: int = 200, deg: int = 20, @@ -77,7 +77,6 @@ def sl_trace ( [1] Ubaru, S., Chen, J., & Saad, Y. (2017). Fast estimation of tr(f(A)) via stochastic Lanczos quadrature. SIAM Journal on Matrix Analysis and Applications, 38(4), 1075-1099. """ - # assert isinstance(A, spmatrix) or isinstance(A, sparray), "A must be a sparse matrix, for now." attr_checks = [hasattr(A, "__matmul__"), hasattr(A, "matmul"), hasattr(A, "dot"), hasattr(A, "matvec")] assert any(attr_checks), "Invalid operator; must have an overloaded 'matvec' or 'matmul' method" assert hasattr(A, "shape") and len(A.shape) >= 2, "Operator must be at least two dimensional." @@ -242,7 +241,7 @@ def sl_trace ( def sl_gauss( - A: Union[LinearOperator, sparray, np.ndarray], + A: Union[LinearOperator, np.ndarray], n: int = 150, deg: int = 20, pdf: str = "rademacher",