diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml deleted file mode 100644 index ff698ef..0000000 --- a/.ci/azure-pipelines.yml +++ /dev/null @@ -1,101 +0,0 @@ -trigger: - - master - -variables: - package_name: omikuji - python.architecture: 'x64' - -stages: - - stage: Rust - jobs: - - job: RustBuild - pool: - vmImage: 'ubuntu-latest' - steps: - - template: azure-setup-rust.yml - - script: | - set -e -x - - cargo fmt --all -- --check - cargo test --features cli - - cd c-api - cargo fmt --all -- --check - cargo test - displayName: 'Cargo test' - - script: | - set -e -x - - cargo install --path . --features cli --locked - omikuji --help - - cd examples - bash run_cli.sh - displayName: 'Run example' - - - stage: Python - jobs: - - job: SDist - pool: - vmImage: 'ubuntu-latest' - variables: - python.version: 3.8 - steps: - - template: azure-python-build.yml - parameters: - sdist: true - - - job: manylinux - pool: - vmImage: 'ubuntu-latest' - container: 'quay.io/pypa/manylinux2014_x86_64:latest' - strategy: - matrix: - Python3.8: - python.version: 3.8 - Python3.9: - python.version: 3.9 - Python3.10: - python.version: 3.10 - Python3.11: - python.version: 3.11 - Python3.12: - python.version: 3.12 - steps: - - template: azure-python-build.yml - parameters: - manylinux: true - - - job: Windows - pool: - vmImage: 'windows-2022' - strategy: - matrix: - Python3.8: - python.version: 3.8 - Python3.9: - python.version: 3.9 - Python3.10: - python.version: 3.10 - steps: - - template: azure-python-build.yml - - - job: macOS - pool: - vmImage: 'macOS-11' - variables: - SYSTEM_VERSION_COMPAT: 0 # Make Python detect macOS version as 11.x instead 10.y - strategy: - matrix: - Python3.8: - python.version: 3.8 - Python3.9: - python.version: 3.9 - Python3.10: - python.version: 3.10 - Python3.11: - python.version: 3.11 - Python3.12: - python.version: 3.12 - steps: - - template: azure-python-build.yml diff --git a/.ci/azure-python-build.yml b/.ci/azure-python-build.yml deleted file mode 100644 index 08a48c3..0000000 --- a/.ci/azure-python-build.yml +++ /dev/null @@ -1,178 +0,0 @@ -parameters: - sdist: false - manylinux: false - -steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '$(python.version)' - architecture: '$(python.architecture)' - - - bash: echo "##vso[task.setvariable variable=pybin;]python" - displayName: 'Choose default Python binary' - condition: succeeded() - - - task: BatchScript@1 - displayName: 'VsDevCmd.bat' - inputs: - filename: C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\Common7\\Tools\\VsDevCmd.bat - arguments: -no_logo -arch=x64 - modifyEnvironment: true - condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) - - - bash: | - set -e - - case $(python.version) in - 3.8) - LANG_ABI_VER="cp38-cp38" - ;; - 3.9) - LANG_ABI_VER="cp39-cp39" - ;; - 3.10) - LANG_ABI_VER="cp310-cp310" - ;; - 3.11) - LANG_ABI_VER="cp311-cp311" - ;; - 3.12) - LANG_ABI_VER="cp312-cp312" - ;; - esac - - echo "##vso[task.setvariable variable=pybin;]/opt/python/${LANG_ABI_VER}/bin/python" - displayName: 'Choose Python for manylinux' - condition: and(succeeded(), eq(${{ parameters.manylinux }}, 'true')) - - - bash: | - set -e - - case $(python.version) in - 3.8) - PY_FULL_VERSION=3.8.10 - MACOS_VERSION=x10.9 - ;; - 3.9) - PY_FULL_VERSION=3.9.12 - MACOS_VERSION=x10.9 - ;; - 3.10) - PY_FULL_VERSION=3.10.4 - MACOS_VERSION=11 - ;; - 3.11) - PY_FULL_VERSION=3.11.6 - MACOS_VERSION=11 - ;; - 3.12) - PY_FULL_VERSION=3.12.0 - MACOS_VERSION=11 - ;; - esac - - INSTALLER_NAME=python-$PY_FULL_VERSION-macos$MACOS_VERSION.pkg - curl https://www.python.org/ftp/python/$PY_FULL_VERSION/$INSTALLER_NAME --output $INSTALLER_NAME - sudo installer -pkg $INSTALLER_NAME -target / - - echo "##vso[task.setvariable variable=PATH]$PATH:/Users/runner/Library/Python/$(python.version)/bin" - - PYBIN=/usr/local/bin/python$(python.version) - curl https://bootstrap.pypa.io/get-pip.py | "$PYBIN" - echo "##vso[task.setvariable variable=pybin]$PYBIN" - displayName: 'Install & Choose Python for Mac' - condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) - - - bash: | - set -e -x - which "$(pybin)" - "$(pybin)" -c "import sys; print(sys.version)" - displayName: 'Query Python version' - - - bash: | - set -e -x - "$(pybin)" -m pip -vvv install --user --upgrade pip - "$(pybin)" -m pip -vvv install --user --upgrade wheel "setuptools>=38.6.0" twine "milksnake>=0.1.6" - "$(pybin)" -m pip --version - "$(pybin)" -m wheel version - "$(pybin)" -m twine --version - displayName: 'Install dev dependencies' - - - bash: | - set -e -x - "$(pybin)" -m pip install --user black - "$(pybin)" -m black --check ./ - displayName: 'Check Python code format' - - - bash: | - "$(pybin)" setup.py sdist - displayName: 'Build source distribution' - condition: and(succeeded(), eq(${{ parameters.sdist }}, 'true')) - - - template: azure-setup-rust.yml - - - bash: | - "$(pybin)" -m pip -vvv install --user dist/* - displayName: 'Test install from source distribution' - condition: and(succeeded(), eq(${{ parameters.sdist }}, 'true')) - - - script: | - "$(pybin)" -m pip -vvv wheel . -w wheelhouse/ - displayName: 'Build wheels' - condition: and(succeeded(), ne(${{ parameters.sdist }}, 'true')) - - - bash: | - set -e -x - for whl in wheelhouse/$(package_name)-*.whl; do - auditwheel repair "$whl" -w wheelhouse/ - done - ls -lh wheelhouse/ - displayName: 'auditwheel repair (manylinux)' - condition: and(succeeded(), eq(${{ parameters.manylinux }}, 'true'), ne(${{ parameters.sdist }}, 'true')) - - - bash: | - set -e -x - "$(pybin)" -m pip install --user delocate - delocate-wheel --version - delocate-wheel wheelhouse/$(package_name)-*.whl - ls -lh wheelhouse/ - displayName: 'Delocate wheels (macOS)' - condition: and(succeeded(), ne(${{ parameters.sdist }}, 'true'), eq(variables['Agent.OS'], 'Darwin')) - - - bash: | - set -e -x - ls -lh wheelhouse/ - - "$(pybin)" -m pip install $(package_name) --no-index -f wheelhouse/ --user - "$(pybin)" -c "import sys; sys.path.remove(''); import $(package_name); print('Installed version: ' + $(package_name).__version__)" - - cd examples - "$(pybin)" run_py.py - - "$(pybin)" -m pip uninstall -y $(package_name) - displayName: 'Test installing wheels and run examples' - condition: and(succeeded(), ne(${{ parameters.sdist }}, 'true')) - - - bash: | - set -e -x - mkdir -p dist - cp wheelhouse/$(package_name)*.whl dist/ - displayName: 'Prepare wheels for publish' - condition: and(succeeded(), ne(${{ parameters.sdist }}, 'true'), ne(${{ parameters.manylinux }}, 'true')) - - - bash: | - set -e -x - mkdir -p dist - cp wheelhouse/$(package_name)*manylinux*.whl dist/ - displayName: 'Prepare wheels for publish (manylinux)' - condition: and(succeeded(), ne(${{ parameters.sdist }}, 'true'), eq(${{ parameters.manylinux }}, 'true')) - - - bash: | - set -e -x - "$(pybin)" -m twine check dist/* - displayName: 'Check artifacts with twine' - - - task: PublishPipelineArtifact@1 - inputs: - targetPath: 'dist' - artifactName: '$(Agent.JobName) $(python.architecture)' diff --git a/.ci/azure-setup-rust.yml b/.ci/azure-setup-rust.yml deleted file mode 100644 index 252524b..0000000 --- a/.ci/azure-setup-rust.yml +++ /dev/null @@ -1,26 +0,0 @@ -steps: - - script: | - set -e - curl https://sh.rustup.rs -sSf | sh -s -- -y - echo "##vso[task.setvariable variable=PATH]$PATH:$HOME/.cargo/bin" - displayName: "Install Rust (*nix)" - condition: not(eq(variables['Agent.OS'], 'Windows_NT')) - - - script: | - set -e - curl -sSf -o rustup-init.exe https://win.rustup.rs - rustup-init.exe -y --default-toolchain %RUSTUP_TOOLCHAIN% - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - echo "##vso[task.setvariable variable=PATH]%PATH%;%USERPROFILE%\.cargo\bin" - displayName: "Install Rust (Windows)" - condition: eq(variables['Agent.OS'], 'Windows_NT') - - - script: rustup component add rustfmt - displayName: "Install rustfmt" - - - script: | - rustup -V - rustup component list --installed - rustc -Vv - cargo -V - displayName: "Query Rust and Cargo versions" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..de8248c --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,344 @@ +name: CI +on: + push: + branches: [main] + tags: ['*'] + pull_request: + workflow_dispatch: + +env: + package_name: omikuji2 + +jobs: + rust-build: + # Skip push events from branches containing a dot (e.g. v2.0-feature) — only run for main branch, tags, PRs, and manual dispatch + if: ${{ github.event_name != 'push' || github.ref_type != 'branch' || !contains(github.ref, '.') }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: | + set -e + curl https://sh.rustup.rs -sSf | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - name: Install rustfmt + run: rustup component add rustfmt + - name: Query Rust and Cargo versions + run: | + rustup -V + rustup component list --installed + rustc -Vv + cargo -V + - name: Cargo test + run: | + set -e -x + cargo fmt --all -- --check + cargo test --features cli + cd python-wrapper + cargo fmt --all -- --check + cargo test + - name: Run example + run: | + set -e -x + cargo install --path . --features cli --locked + omikuji --help + cd examples + bash run_cli.sh + + python-sdist: + # Skip push events from branches containing a dot (e.g. v2.0-feature) — only run for main branch, tags, PRs, and manual dispatch + if: ${{ github.event_name != 'push' || github.ref_type != 'branch' || !contains(github.ref, '.') }} + runs-on: ubuntu-latest + env: + PYTHON_VERSION: '3.14' + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: | + set -e + curl https://sh.rustup.rs -sSf | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - name: Install rustfmt + run: rustup component add rustfmt + - name: Query Rust and Cargo versions + run: | + rustup -V + rustup component list --installed + rustc -Vv + cargo -V + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Query Python version + run: | + set -e -x + which python + python -c "import sys; print(sys.version)" + - name: Install dev dependencies + run: | + set -e -x + python -m pip -vvv install --user --upgrade pip wheel "packaging>=26.2" twine maturin + python -m pip --version + python -m wheel version + python -m twine --version + python -m maturin --version + - name: Check Python code format + run: | + python -m pip install --user black + python -m black --check ./ + - name: Build source distribution + run: | + python -m maturin sdist --out dist/ + - name: Test install from source distribution + run: | + python -m pip -vvv install --user dist/* + - name: Check artifacts with twine + run: | + set -e -x + python -m twine check dist/* + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.job }}-${{ env.PYTHON_VERSION }}-${{ runner.OS }}-${{ runner.Arch }} + path: dist/ + + python-manylinux: + # Skip push events from branches containing a dot (e.g. v2.0-feature) — only run for main branch, tags, PRs, and manual dispatch + if: ${{ github.event_name != 'push' || github.ref_type != 'branch' || !contains(github.ref, '.') }} + runs-on: ubuntu-latest + container: quay.io/pypa/manylinux_2_28_x86_64:latest + env: + manylinux: true + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] + steps: + - uses: actions/checkout@v4 + - name: Set PYBIN for manylinux Python + # The manylinux pypa image has multiple Python versions installed in non-standard locations, + # so we need to set PYBIN accordingly for the rest of the steps. + run: | + case ${{ matrix.python-version }} in + 3.10) PYBIN=/opt/python/cp310-cp310/bin/python ;; + 3.11) PYBIN=/opt/python/cp311-cp311/bin/python ;; + 3.12) PYBIN=/opt/python/cp312-cp312/bin/python ;; + 3.13) PYBIN=/opt/python/cp313-cp313/bin/python ;; + 3.14) PYBIN=/opt/python/cp314-cp314/bin/python ;; + esac + echo "PYBIN=$PYBIN" >> $GITHUB_ENV + - name: Query Python version + run: | + set -e -x + which "$PYBIN" + "$PYBIN" -c "import sys; print(sys.version)" + - name: Install dev dependencies + run: | + set -e -x + "$PYBIN" -m pip -vvv install --user --upgrade pip wheel "packaging>=26.2" twine maturin + "$PYBIN" -m pip --version + "$PYBIN" -m wheel version + "$PYBIN" -m twine --version + "$PYBIN" -m maturin --version + - name: Check Python code format + run: | + "$PYBIN" -m pip install --user black + "$PYBIN" -m black --check ./ + - name: Install Rust (Linux/macOS) + run: | + set -e + curl https://sh.rustup.rs -sSf | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - name: Install rustfmt + run: rustup component add rustfmt + - name: Query Rust and Cargo versions + run: | + rustup -V + rustup component list --installed + rustc -Vv + cargo -V + - name: Build wheels with maturin + run: | + "$PYBIN" -m maturin build --release --interpreter "$PYBIN" --out wheelhouse/ + - name: auditwheel repair (manylinux) + run: | + set -e -x + for whl in wheelhouse/omikuji2*-manylinux*.whl; do + auditwheel repair "$whl" -w wheelhouse/ + done + ls -lh wheelhouse/ + - name: Test installing wheels and run examples + run: | + set -e -x + ls -lh wheelhouse/ + "$PYBIN" -m pip install omikuji2 --no-index -f wheelhouse/ --user + "$PYBIN" -c "import sys; sys.path.remove(''); import omikuji; print('Installed version: ' + omikuji.__version__)" + cd examples + "$PYBIN" run_py.py + "$PYBIN" -m pip uninstall -y omikuji2 + - name: Prepare wheels for publish (manylinux) + run: | + set -e -x + mkdir -p dist + cp wheelhouse/omikuji2*manylinux*.whl dist/ + - name: Check artifacts with twine + run: | + set -e -x + "$PYBIN" -m twine check dist/* + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.job }}-${{ matrix.python-version }}-${{ runner.OS }}-${{ runner.Arch }} + path: dist/ + + python-windows: + # Skip push events from branches containing a dot (e.g. v2.0-feature) — only run for main branch, tags, PRs, and manual dispatch + if: ${{ github.event_name != 'push' || github.ref_type != 'branch' || !contains(github.ref, '.') }} + runs-on: windows-2022 + strategy: + matrix: + python-version: ['3.10'] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Query Python version + shell: bash + run: | + set -e -x + python -c "import sys; print(sys.version)" + - name: Install dev dependencies + shell: bash + run: | + set -e -x + python -m pip -vvv install --user --upgrade pip wheel "packaging>=26.2" twine maturin + python -m pip --version + python -m wheel version + python -m twine --version + maturin --version + - name: Check Python code format + shell: bash + run: | + python -m pip install --user black + python -m black --check ./ + - name: Install Rust (Windows) + shell: bash + run: | + set -e + curl -sSf -o rustup-init.exe https://win.rustup.rs + ./rustup-init.exe -y + echo "$USERPROFILE/.cargo/bin" >> $GITHUB_PATH + - name: Install rustfmt + shell: bash + run: rustup component add rustfmt + - name: Query Rust and Cargo versions + shell: bash + run: | + rustup -V + rustup component list --installed + rustc -Vv + cargo -V + - name: Build wheels with maturin + shell: cmd + run: >- + "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" -no_logo -arch=x64 + && maturin build --release --interpreter python --out wheelhouse/ + - name: Test installing wheels and run examples + shell: bash + run: | + set -e -x + ls -lh wheelhouse/ + python -m pip install omikuji2 --no-index -f wheelhouse/ --user + python -c "import sys; sys.path.remove(''); import omikuji; print('Installed version: ' + omikuji.__version__)" + cd examples + python run_py.py + python -m pip uninstall -y omikuji2 + - name: Prepare wheels for publish + shell: bash + run: | + set -e -x + mkdir -p dist + cp wheelhouse/omikuji2*.whl dist/ + - name: Check artifacts with twine + shell: bash + run: | + set -e -x + python -m twine check dist/* + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.job }}-${{ matrix.python-version }}-${{ runner.OS }}-${{ runner.Arch }} + path: dist/ + + python-macos: + # Skip push events from branches containing a dot (e.g. v2.0-feature) — only run for main branch, tags, PRs, and manual dispatch + if: ${{ github.event_name != 'push' || github.ref_type != 'branch' || !contains(github.ref, '.') }} + runs-on: macos-15 + env: + SYSTEM_VERSION_COMPAT: 0 + strategy: + matrix: + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] + steps: + - uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Query Python version + run: | + set -e -x + which python + python -c "import sys; print(sys.version)" + - name: Install dev dependencies + run: | + set -e -x + python -m pip -vvv install --user --upgrade pip wheel twine maturin + python -m pip --version + python -m wheel version + python -m twine --version + - name: Check Python code format + run: | + python -m pip install --user black + python -m black --check ./ + - name: Install Rust (Linux/macOS) + run: | + set -e + curl https://sh.rustup.rs -sSf | sh -s -- -y + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + - name: Install rustfmt + run: rustup component add rustfmt + - name: Query Rust and Cargo versions + run: | + rustup -V + rustup component list --installed + rustc -Vv + cargo -V + - name: Build wheels with maturin + run: | + python -m maturin build --release --out wheelhouse/ + - name: Test installing wheels and run examples + run: | + set -e -x + ls -lh wheelhouse/ + python -m pip install omikuji2 --no-index -f wheelhouse/ --user + python -c "import sys; sys.path.remove(''); import omikuji; print('Installed version: ' + omikuji.__version__)" + cd examples + python run_py.py + python -m pip uninstall -y omikuji2 + - name: Prepare wheels for publish + run: | + set -e -x + mkdir -p dist + cp wheelhouse/omikuji2*.whl dist/ + - name: Check artifacts with twine + run: | + set -e -x + python -m twine check dist/* + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.job }}-${{ matrix.python-version }}-${{ runner.OS }}-${{ runner.Arch }} + path: dist/ diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..04a0d8d --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,35 @@ +name: Publish release + +on: + workflow_run: + workflows: ["CI"] + types: [completed] + +permissions: + contents: read + +jobs: + publish-to-pypi: + if: ${{ startsWith(github.event.workflow_run.head_branch, 'v') && contains(github.event.workflow_run.head_branch, '.') && github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + environment: + name: testpypi + url: https://test.pypi.org/project/omikuji2/ + permissions: + contents: read + actions: read + id-token: write + steps: + - name: Download wheel artifacts + uses: actions/download-artifact@v4 + with: + run-id: ${{ github.event.workflow_run.id }} + pattern: python-* + path: dist/ + merge-multiple: true + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish package distributions to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ diff --git a/Cargo.lock b/Cargo.lock index d974f2d..4f0552a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,12 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", @@ -27,56 +27,58 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] @@ -96,27 +98,21 @@ checksum = "3c07dab4369547dbe5114677b33fbbf724971019f3818172d59a97a61c774ffd" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "clap" -version = "4.4.7" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", @@ -124,9 +120,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.7" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", @@ -154,19 +150,18 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" -version = "2.0.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ - "is-terminal", "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -177,77 +172,55 @@ checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "deranged" -version = "0.3.9" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" -dependencies = [ - "powerfmt", -] +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" [[package]] name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - -[[package]] -name = "errno" -version = "0.3.5" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -256,15 +229,15 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -278,20 +251,15 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] -name = "is-terminal" -version = "0.4.9" +name = "is_terminal_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.48.0", -] +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -304,39 +272,33 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "linux-raw-sys" -version = "0.4.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "log" -version = "0.4.20" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "matrixmultiply" @@ -348,13 +310,10 @@ dependencies = [ ] [[package]] -name = "memoffset" -version = "0.9.0" +name = "memchr" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "ndarray" @@ -389,21 +348,26 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -411,9 +375,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", @@ -421,9 +385,9 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] @@ -455,9 +419,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "order-stat" @@ -499,33 +469,36 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha", @@ -559,9 +532,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" dependencies = [ "either", "rayon-core", @@ -569,45 +542,21 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] -[[package]] -name = "rustix" -version = "0.38.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "ryu" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "serde" -version = "1.0.190" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -621,11 +570,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -634,13 +592,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] @@ -657,9 +617,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "sprs" @@ -685,9 +645,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.38" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -696,58 +656,59 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", - "itoa", "libc", + "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ + "num-conv", "time-core", ] [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "winapi" @@ -771,6 +732,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.42.0" @@ -788,26 +755,36 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -818,9 +795,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -830,9 +807,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -842,9 +819,15 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -854,9 +837,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -866,9 +849,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -878,9 +861,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -890,26 +873,32 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "zerocopy" -version = "0.7.20" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd66a62464e3ffd4e37bd09950c2b9dd6c4f8767380fabba0d523f9a775bc85a" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.20" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "255c4596d41e6916ced49cfafea18727b24d67878fa180ddfd69b9df34fd1726" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/MANIFEST.in b/MANIFEST.in index b14fbe1..a55a663 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include Cargo.toml Cargo.lock -include c-api/Cargo.toml c-api/Cargo.lock +include python-wrapper/Cargo.toml recursive-include . *.rs prune target -prune c-api/target +prune python-wrapper/target diff --git a/README.md b/README.md index 66288cc..113d51f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Omikuji -[![Build Status](https://dev.azure.com/yubingdong/omikuji/_apis/build/status/tomtung.omikuji?branchName=master)](https://dev.azure.com/yubingdong/omikuji/_build/latest?definitionId=1&branchName=master) [![Crate version](https://img.shields.io/crates/v/omikuji)](https://crates.io/crates/omikuji) [![PyPI version](https://img.shields.io/pypi/v/omikuji)](https://pypi.org/project/omikuji/) +[![CI pipeline](https://github.com/NatLibFi/omikuji/actions/workflows/ci.yaml/badge.svg)](https://github.com/NatLibFi/omikuji/actions/workflows/ci.yaml) +[![Crate version](https://img.shields.io/crates/v/omikuji)](https://crates.io/crates/omikuji) [![PyPI version](https://img.shields.io/pypi/v/omikuji)](https://pypi.org/project/omikuji/) An efficient implementation of Partitioned Label Trees (Prabhu et al., 2018) and its variations for extreme multi-label classification, written in Rust🦀 with love💖. diff --git a/c-api/Cargo.lock b/c-api/Cargo.lock deleted file mode 100644 index f2a879f..0000000 --- a/c-api/Cargo.lock +++ /dev/null @@ -1,980 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "alga" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f823d037a7ec6ea2197046bafd4ae150e6bc36f9ca347404f46a46823fa84f2" -dependencies = [ - "approx", - "num-complex 0.2.4", - "num-traits", -] - -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] -name = "approx" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" -dependencies = [ - "num-traits", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" - -[[package]] -name = "cbindgen" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49" -dependencies = [ - "clap", - "heck", - "indexmap", - "log", - "proc-macro2", - "quote", - "serde", - "serde_json", - "syn 1.0.109", - "tempfile", - "toml", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_lex", - "indexmap", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "colored" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" -dependencies = [ - "is-terminal", - "lazy_static", - "windows-sys 0.48.0", -] - -[[package]] -name = "const-default" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" - -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "deranged" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - -[[package]] -name = "errno" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "fastrand" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi 0.3.3", - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "linux-raw-sys" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" - -[[package]] -name = "log" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" - -[[package]] -name = "matrixmultiply" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" -dependencies = [ - "rawpointer", -] - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "ndarray" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c0d5c9540a691d153064dc47a4db2504587a75eae07bf1d73f7a596ebc73c04" -dependencies = [ - "matrixmultiply", - "num-complex 0.3.1", - "num-integer", - "num-traits", - "rawpointer", - "serde", -] - -[[package]] -name = "num-complex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.3", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "omikuji" -version = "0.5.1" -dependencies = [ - "const-default", - "hashbrown 0.14.2", - "itertools", - "log", - "ndarray", - "num-traits", - "order-stat", - "ordered-float", - "pbr", - "pdqselect", - "rand", - "rayon", - "serde", - "serde_cbor", - "serde_json", - "sprs", -] - -[[package]] -name = "omikuji_c-api" -version = "0.5.1" -dependencies = [ - "cbindgen", - "itertools", - "libc", - "omikuji", - "simple_logger", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "order-stat" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb" - -[[package]] -name = "ordered-float" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bcbab4bfea7a59c2c0fe47211a1ac4e3e96bea6eb446d704f310bc5c732ae2" -dependencies = [ - "num-traits", -] - -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - -[[package]] -name = "pbr" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5827dfa0d69b6c92493d6c38e633bbaa5937c153d0d7c28bf12313f8c6d514" -dependencies = [ - "crossbeam-channel", - "libc", - "winapi", -] - -[[package]] -name = "pdqselect" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7778906d9321dd56cde1d1ffa69a73e59dcf5fda6d366f62727adf2bd4193aee" - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro2" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - -[[package]] -name = "rayon" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "rustix" -version = "0.38.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" -dependencies = [ - "bitflags 2.4.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "ryu" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.190" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.190" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", -] - -[[package]] -name = "serde_json" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "simple_logger" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2230cd5c29b815c9b699fb610b49a5ed65588f3509d9f0108be3a885da629333" -dependencies = [ - "colored", - "log", - "time", - "windows-sys 0.42.0", -] - -[[package]] -name = "smallvec" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" - -[[package]] -name = "sprs" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed3b0e2c934e8eb2592a534134c1149869e4b2e55bd4086017d342be6afaea4" -dependencies = [ - "alga", - "ndarray", - "num-complex 0.2.4", - "num-traits", - "num_cpus", - "rayon", - "serde", - "smallvec", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "termcolor" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - -[[package]] -name = "time" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" -dependencies = [ - "deranged", - "itoa", - "libc", - "num_threads", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" -dependencies = [ - "time-core", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "zerocopy" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd66a62464e3ffd4e37bd09950c2b9dd6c4f8767380fabba0d523f9a775bc85a" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "255c4596d41e6916ced49cfafea18727b24d67878fa180ddfd69b9df34fd1726" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", -] diff --git a/c-api/Cargo.toml b/c-api/Cargo.toml deleted file mode 100644 index f486498..0000000 --- a/c-api/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "omikuji_c-api" -version = "0.5.1" -edition = "2021" -authors = ["Tom Dong "] - -[lib] -name = "omikuji" -crate-type = ["staticlib", "cdylib"] - -[dependencies] -itertools = "0.11.*" -libc = "0.2.*" -omikuji = { path = ".." } -simple_logger = "4.2.*" - -[build-dependencies] -cbindgen = "0.26.*" - -[profile.release] -panic = "abort" -lto = true -codegen-units = 1 diff --git a/c-api/build.rs b/c-api/build.rs deleted file mode 100644 index c2be64d..0000000 --- a/c-api/build.rs +++ /dev/null @@ -1,27 +0,0 @@ -extern crate cbindgen; - -use std::env; -use std::path::Path; - -fn main() { - let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let header_path = Path::new(&crate_dir).join("target/include/"); - - cbindgen::Builder::new() - .with_crate(&crate_dir) - .with_language(cbindgen::Language::C) - .with_include_guard("OMIKUJI_H") - .with_item_prefix("OMIKUJI_") - .generate() - .expect("Unable to generate C bindings") - .write_to_file(header_path.join("omikuji.h")); - - cbindgen::Builder::new() - .with_crate(&crate_dir) - .with_language(cbindgen::Language::Cxx) - .with_include_guard("OMIKUJI_H") - .with_namespace("omikuji") - .generate() - .expect("Unable to generate C++ bindings") - .write_to_file(header_path.join("omikuji.hpp")); -} diff --git a/c-api/src/lib.rs b/c-api/src/lib.rs deleted file mode 100644 index dfff6d5..0000000 --- a/c-api/src/lib.rs +++ /dev/null @@ -1,410 +0,0 @@ -use itertools::Itertools; -use libc::size_t; -use omikuji::rayon; -use std::convert::TryInto; -use std::ffi::CStr; -use std::os::raw::{c_char, c_float, c_void}; -use std::slice; - -#[repr(C)] -pub struct Model { - _private: [u8; 0], -} - -#[repr(C)] -pub struct DataSet { - _private: [u8; 0], -} - -#[repr(C)] -pub struct ThreadPool { - _private: [u8; 0], -} - -/// Initialize a thread pool for later use. -/// -/// # Safety -/// The caller is responsible for freeing the returned pointer by calling -/// [free_omikuji_thread_pool()]. -/// -#[no_mangle] -pub unsafe extern "C" fn init_omikuji_thread_pool(n_threads: usize) -> *mut ThreadPool { - let thread_pool = rayon::ThreadPoolBuilder::new() - .num_threads(n_threads) - .stack_size(32 * 1024 * 1024) - .build() - .unwrap(); - Box::into_raw(Box::new(thread_pool)) as *mut ThreadPool -} - -/// Free the thread pool object. -/// -/// # Safety -/// The input pointer must have been obtained by calling [init_omikuji_thread_pool()]. The caller -/// is also responsible for ensuring not freeing the same pointer more than once. -/// -#[no_mangle] -pub unsafe extern "C" fn free_omikuji_thread_pool(ptr: *mut ThreadPool) { - if !ptr.is_null() { - let ptr = ptr as *mut c_void as *mut rayon::ThreadPool; - drop(Box::from_raw(ptr)); - } -} - -unsafe fn maybe_run_with_thread_pool(thread_pool_ptr: *const ThreadPool, op: OP) -> R -where - OP: FnOnce() -> R + Send, - R: Send, -{ - let thread_pool_ptr = thread_pool_ptr as *const c_void as *const rayon::ThreadPool; - if thread_pool_ptr.is_null() { - op() - } else { - (*thread_pool_ptr).install(op) - } -} - -/// Load omikuji model from the given directory. -/// -/// # Safety -/// The path pointer must point to a valid C string. -/// The caller is responsible for freeing the returned pointer by calling [free_omikuji_model()]. -/// -#[no_mangle] -pub unsafe extern "C" fn load_omikuji_model(path: *const c_char) -> *mut Model { - assert!(!path.is_null(), "Path should not be null"); - let maybe_model = CStr::from_ptr(path) - .to_str() - .map_err(|e| format!("Failed to parse path: {}", e)) - .and_then(|path| { - omikuji::Model::load(path).map_err(|e| format!("Failed to load model: {}", e)) - }); - - match maybe_model { - Ok(model) => Box::into_raw(Box::new(model)) as *mut Model, - Err(msg) => { - eprintln!("{}", msg); - std::ptr::null_mut() - } - } -} - -/// Save omikuji model to the given directory. -/// -/// # Safety -/// The input model pointer must have been obtained by calling [load_omikuji_model()] or -/// [train_omikuji_model()]. The path pointer must point to a valid C string. -/// -#[no_mangle] -pub unsafe extern "C" fn save_omikuji_model(model_ptr: *mut Model, path: *const c_char) -> i8 { - assert!(!model_ptr.is_null(), "Model should not be null"); - assert!(!path.is_null(), "Path should not be null"); - let model_ptr = model_ptr as *mut c_void as *mut omikuji::Model; - if let Err(msg) = CStr::from_ptr(path) - .to_str() - .map_err(|e| format!("Failed to parse path: {}", e)) - .and_then(|path| { - (*model_ptr) - .save(path) - .map_err(|e| format!("Failed to save model: {}", e)) - }) - { - eprintln!("{}", msg); - -1 - } else { - 0 - } -} - -/// Free omikuji model from memory. -/// -/// # Safety -/// The input model pointer must have been obtained by calling [load_omikuji_model()] or -/// [train_omikuji_model()]. The caller is also responsible for ensuring not freeing the same -/// pointer more than once. -/// -#[no_mangle] -pub unsafe extern "C" fn free_omikuji_model(model_ptr: *mut Model) { - if !model_ptr.is_null() { - let model_ptr = model_ptr as *mut c_void as *mut omikuji::Model; - drop(Box::from_raw(model_ptr)); - } -} - -/// Densify model weights to speed up prediction at the cost of more memory usage. -/// -/// # Safety -/// The model pointer must have been obtained by calling [load_omikuji_model()] or -/// [train_omikuji_model()]. The thread pool pointer must have been obtained by calling -/// [init_omikuji_thread_pool()]. -/// -#[no_mangle] -pub unsafe extern "C" fn densify_omikuji_model( - model_ptr: *mut Model, - max_sparse_density: f32, - thread_pool_ptr: *const ThreadPool, -) { - assert!(!model_ptr.is_null(), "Model should not be null"); - let model = &mut *(model_ptr as *mut c_void as *mut omikuji::Model); - maybe_run_with_thread_pool(thread_pool_ptr, || { - model.densify_weights(max_sparse_density) - }); -} - -/// Get the expected dimension of feature vectors. -/// -/// # Safety -/// The model pointer must have been obtained by calling [load_omikuji_model()] or -/// [train_omikuji_model()]. -/// -#[no_mangle] -pub unsafe extern "C" fn omikuji_n_features(model_ptr: *const Model) -> size_t { - assert!(!model_ptr.is_null(), "Model should not be null"); - let model_ptr = model_ptr as *const c_void as *const omikuji::Model; - (*model_ptr).n_features() -} - -/// The number of trees in the forest model. -/// -/// # Safety -/// The model pointer must have been obtained by calling [load_omikuji_model()] or -/// [train_omikuji_model()]. -/// -#[no_mangle] -pub unsafe extern "C" fn omikuji_n_trees(model_ptr: *const Model) -> size_t { - assert!(!model_ptr.is_null(), "Model should not be null"); - let model_ptr = model_ptr as *const c_void as *const omikuji::Model; - (*model_ptr).n_trees() -} - -/// Make predictions with omikuji model. -/// -/// # Safety -/// The model pointer must have been obtained by calling [load_omikuji_model()] or -/// [train_omikuji_model()]. The thread pool pointer must have been obtained by calling -/// [init_omikuji_thread_pool()]. [feature_indices], [feature_values], [output_labels], and -/// [output_scores] must point to valid arrays of their respective type. -/// -#[no_mangle] -pub unsafe extern "C" fn omikuji_predict( - model_ptr: *const Model, - beam_size: size_t, - input_len: size_t, - feature_indices: *const u32, - feature_values: *const c_float, - output_len: size_t, - output_labels: *mut u32, - output_scores: *mut c_float, - thread_pool_ptr: *const ThreadPool, -) -> size_t { - assert!(!model_ptr.is_null(), "Model should not be null"); - let model = &*(model_ptr as *mut c_void as *mut omikuji::Model); - let feature_vec = { - let feature_indices = slice::from_raw_parts(feature_indices, input_len); - let feature_values = slice::from_raw_parts(feature_values, input_len); - feature_indices - .iter() - .cloned() - .zip_eq(feature_values.iter().cloned()) - .collect_vec() - }; - - let predictions = - maybe_run_with_thread_pool(thread_pool_ptr, || model.predict(&feature_vec, beam_size)); - - let output_len = output_len.min(predictions.len()); - let output_labels = slice::from_raw_parts_mut(output_labels, output_len); - let output_scores = slice::from_raw_parts_mut(output_scores, output_len); - for (i, (label, score)) in predictions.into_iter().take(output_len).enumerate() { - output_labels[i] = label; - output_scores[i] = score; - } - - output_len -} - -/// Load a data file from the Extreme Classification Repository. -/// -/// # Safety -/// The thread pool pointer must have been obtained by calling [init_omikuji_thread_pool()]. -/// The path pointer must point to a valid C string. The caller is responsible for freeing -/// the returned pointer by calling [free_omikuji_data_set()]. -/// -#[no_mangle] -pub unsafe extern "C" fn load_omikuji_data_set( - path: *const c_char, - thread_pool_ptr: *const ThreadPool, -) -> *mut DataSet { - assert!(!path.is_null(), "Path should not be null"); - match CStr::from_ptr(path) - .to_str() - .map_err(|_| "Failed to parse path") - .and_then(|path| { - maybe_run_with_thread_pool(thread_pool_ptr, || { - omikuji::DataSet::load_xc_repo_data_file(path) - .map_err(|_| "Failed to laod data file") - }) - }) { - Ok(dataset) => Box::into_raw(Box::new(dataset)) as *mut DataSet, - Err(msg) => { - eprintln!("{}", msg); - std::ptr::null_mut() - } - } -} - -/// Free data set object. -/// -/// # Safety -/// The input pointer must have been obtained by calling [load_omikuji_data_set()]. The caller -/// is also responsible for ensuring not freeing the same pointer more than once. -/// -#[no_mangle] -pub unsafe extern "C" fn free_omikuji_data_set(dataset_ptr: *mut DataSet) { - if !dataset_ptr.is_null() { - let dataset_ptr = dataset_ptr as *mut c_void as *mut omikuji::DataSet; - drop(Box::from_raw(dataset_ptr)); - } -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub enum LossType { - Hinge = 0, - Log = 1, -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub struct HyperParam { - pub n_trees: size_t, - pub min_branch_size: size_t, - pub max_depth: size_t, - pub centroid_threshold: f32, - pub collapse_every_n_layers: size_t, - pub tree_structure_only: bool, - pub train_trees_1_by_1: bool, - pub linear_loss_type: LossType, - pub linear_eps: c_float, - pub linear_c: c_float, - pub linear_weight_threshold: c_float, - pub linear_max_iter: u32, - pub cluster_k: size_t, - pub cluster_balanced: bool, - pub cluster_eps: f32, - pub cluster_min_size: usize, -} - -impl From for HyperParam { - fn from(hyper_param: omikuji::model::TrainHyperParam) -> Self { - Self { - n_trees: hyper_param.n_trees, - min_branch_size: hyper_param.min_branch_size, - max_depth: hyper_param.max_depth, - centroid_threshold: hyper_param.centroid_threshold, - collapse_every_n_layers: hyper_param.collapse_every_n_layers, - linear_loss_type: match hyper_param.linear.loss_type { - omikuji::model::liblinear::LossType::Hinge => LossType::Hinge, - omikuji::model::liblinear::LossType::Log => LossType::Log, - }, - linear_eps: hyper_param.linear.eps, - linear_c: hyper_param.linear.c, - linear_weight_threshold: hyper_param.linear.weight_threshold, - linear_max_iter: hyper_param.linear.max_iter, - cluster_k: hyper_param.cluster.k, - cluster_balanced: hyper_param.cluster.balanced, - cluster_eps: hyper_param.cluster.eps, - cluster_min_size: hyper_param.cluster.min_size, - tree_structure_only: hyper_param.tree_structure_only, - train_trees_1_by_1: hyper_param.train_trees_1_by_1, - } - } -} - -impl TryInto for HyperParam { - type Error = String; - - fn try_into(self) -> Result { - let hyper_param = omikuji::model::train::HyperParam { - n_trees: self.n_trees, - min_branch_size: self.min_branch_size, - max_depth: self.max_depth, - centroid_threshold: self.centroid_threshold, - collapse_every_n_layers: self.collapse_every_n_layers, - tree_structure_only: self.tree_structure_only, - train_trees_1_by_1: self.train_trees_1_by_1, - linear: omikuji::model::liblinear::HyperParam { - loss_type: match self.linear_loss_type { - LossType::Hinge => omikuji::model::liblinear::LossType::Hinge, - LossType::Log => omikuji::model::liblinear::LossType::Log, - }, - eps: self.linear_eps, - c: self.linear_c, - weight_threshold: self.linear_weight_threshold, - max_iter: self.linear_max_iter, - }, - cluster: omikuji::model::cluster::HyperParam { - k: self.cluster_k, - balanced: self.cluster_balanced, - eps: self.cluster_eps, - min_size: self.cluster_min_size, - }, - }; - - if let Err(msg) = hyper_param.validate() { - Err(msg) - } else { - Ok(hyper_param) - } - } -} - -/// Get the default training hyper-parameters -#[no_mangle] -pub extern "C" fn omikuji_default_hyper_param() -> HyperParam { - omikuji::model::train::HyperParam::default().into() -} - -/// Train omikuji model on the given data set and hyper-parameters. -/// -/// # Safety -/// The dataset pointer must have been obtained by calling [load_omikuji_data_set()]. -/// The thread pool pointer must have been obtained by calling [init_omikuji_thread_pool()]. -/// The caller is responsible for freeing the returned pointer by calling [free_omikuji_model()]. -/// -#[no_mangle] -pub unsafe extern "C" fn train_omikuji_model( - dataset_ptr: *const DataSet, - hyper_param: HyperParam, - thread_pool_ptr: *const ThreadPool, -) -> *mut Model { - assert!(!dataset_ptr.is_null(), "Dataset should not be null"); - let result: Result = hyper_param.try_into(); - match result { - Ok(hyper_param) => { - let dataset_ptr = dataset_ptr as *const c_void as *const omikuji::DataSet; - // Clone the dataset so that the pointer remains valid - let dataset = (*dataset_ptr).clone(); - - let model = maybe_run_with_thread_pool(thread_pool_ptr, || hyper_param.train(dataset)); - - Box::into_raw(Box::new(model)) as *mut Model - } - Err(msg) => { - eprintln!("Failed to set hyper-parameters: {}", msg); - std::ptr::null_mut() - } - } -} - -/// Initialize a simple logger that writes to stdout. -#[no_mangle] -pub extern "C" fn omikuji_init_logger() -> i8 { - match simple_logger::init() { - Ok(_) => 0, - Err(_) => { - eprintln!("Failed to initialize logger"); - -1 - } - } -} diff --git a/less b/less deleted file mode 100644 index e69de29..0000000 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f7fc799 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" + +[project] +name = "omikuji2" +version = "0.5.1" +description = "Python binding to Omikuji, an efficient implementation of Partitioned Label Trees and its variations for extreme multi-label classification" +readme = "README.md" +requires-python = ">=3.8" +authors = [ + {name = "Tom Dong", email = "tom.tung.dyb@gmail.com"}, +] +license = {text = "MIT"} +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Rust", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Software Development :: Libraries", +] + +[tool.maturin] +manifest-path = "python-wrapper/Cargo.toml" +module-name = "omikuji._omikuji" +python-source = "python-wrapper" diff --git a/python-wrapper/Cargo.toml b/python-wrapper/Cargo.toml new file mode 100644 index 0000000..dfdaa42 --- /dev/null +++ b/python-wrapper/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "omikuji2" +version = "0.5.1" +edition = "2021" +authors = ["Tom Dong "] + +[lib] +name = "_omikuji" +crate-type = ["cdylib"] + +[dependencies] +itertools = "0.11.*" +ndarray = { version = "0.14.*", features = ["serde-1"] } +num-traits = "0.2.*" +order-stat = "0.1.*" +ordered-float = "3.0.*" +pbr = "1.1.*" +rand = "0.8.*" +rayon = "1.8.*" +serde = { version = "1.0.*", features = ["derive"] } +serde_cbor = "0.11.*" +serde_json = "1.0.*" +simple_logger = "4.2.*" +hashbrown = "0.14.*" +pdqselect = "0.1.*" +sprs = { version = "0.9.*", features = ["serde"] } +const-default = "1.0.*" +log = "0.4.*" +pyo3 = { version = "0.29", features = ["extension-module"] } +omikuji = { path = ".." } diff --git a/python-wrapper/omikuji/__init__.py b/python-wrapper/omikuji/__init__.py index 6dab009..382972b 100644 --- a/python-wrapper/omikuji/__init__.py +++ b/python-wrapper/omikuji/__init__.py @@ -1,177 +1,26 @@ __version__ = "0.5.1" -__all__ = ["Model", "LossType"] - -from ._libomikuji import lib, ffi -from enum import Enum -import os -from typing import Optional - - -class LossType(Enum): - HINGE = lib.Hinge - LOG = lib.Log - - -class _ThreadPoolHandle: - def __init__(self, n_threads: Optional[int] = None, *, initialize=False): - self._n_threads = n_threads or 0 - self._ptr = None - self._pid = None - if initialize: - self._reset() - - def _reset(self): - self._pid = os.getpid() - self._ptr = ffi.gc( - lib.init_omikuji_thread_pool(max(self._n_threads, 0)), - lib.free_omikuji_thread_pool, - ) - - @property - def ptr(self): - if self._ptr is None or self._pid != os.getpid(): - # Reset if the thread pool is uninitialized or if a fork has happened - self._reset() - - return self._ptr - - -class Model: - """A Omikuji model object.""" - - def __init__(self, model_ptr, thread_pool: Optional[_ThreadPoolHandle] = None): - """Constructor for internal use only. - - To get model objects, call load or train_on_data instead. - - """ - assert model_ptr != ffi.NULL - self._model_ptr = ffi.gc(model_ptr, lib.free_omikuji_model) - self._thread_pool = thread_pool if thread_pool else _ThreadPoolHandle() - - def init_prediction_thread_pool(self, n_threads: int): - """Initialize the thread pool for processing model prediction. - - If n_threads is set to 0, the number of threads if automatically chosen. - - Omikuji uses Rayon for parallelization. If this method is not called, a thread - pool is initialized when it's first used. - - """ - self._thread_pool = _ThreadPoolHandle(n_threads, initialize=True) - - @classmethod - def load(cls, path: str): - """Load Omikuji model from the given directory.""" - model_ptr = lib.load_omikuji_model(ffi.new("char[]", path.encode())) - if model_ptr == ffi.NULL: - raise RuntimeError("Failed to load model from %s" % (path,)) - - return cls(model_ptr) - - @property - def n_features(self): - """Get the expected dimension of feature vectors.""" - return lib.omikuji_n_features(self._model_ptr) - - @property - def n_trees(self): - """The number of trees in the forest model.""" - return lib.omikuji_n_trees(self._model_ptr) - - def save(self, path): - """Save Omikuji model to the given directory.""" - assert self._model_ptr != ffi.NULL - if ( - lib.save_omikuji_model(self._model_ptr, ffi.new("char[]", path.encode())) - < 0 - ): - raise RuntimeError("Failed to save model to %s" % (path,)) - - def densify_weights( - self, max_sparse_density: float = 0.1, n_threads: Optional[int] = None - ): - """Densify model weights to speed up prediction at the expense of memory usage. - - Note that this method is NOT thread-safe. The caller is responsible for making - sure that no other method call is happening at the same time. - - """ - assert self._model_ptr != ffi.NULL - thread_pool = ( - self._thread_pool if n_threads is None else _ThreadPoolHandle(n_threads) - ) - lib.densify_omikuji_model(self._model_ptr, max_sparse_density, thread_pool.ptr) - - def predict(self, feature_value_pairs, beam_size=10, top_k=10): - """Make predictions with Omikuji model.""" - assert self._model_ptr != ffi.NULL - - feature_value_pairs = sorted(feature_value_pairs, key=lambda kv: kv[0]) - n_features = self.n_features - if feature_value_pairs: - for (f1, _), (f2, _) in zip(feature_value_pairs, feature_value_pairs[1:]): - assert 0 <= f1 < f2 < n_features, "Incorrect feature index" - - input_len = len(feature_value_pairs) - feature_indices = ffi.new("uint32_t[]", input_len) - feature_values = ffi.new("float[]", input_len) - for i, (f, v) in enumerate(feature_value_pairs): - feature_indices[i] = f - feature_values[i] = v - - output_labels = ffi.new("uint32_t[]", top_k) - output_scores = ffi.new("float[]", top_k) - top_k = lib.omikuji_predict( - self._model_ptr, - beam_size, - input_len, - feature_indices, - feature_values, - top_k, - output_labels, - output_scores, - self._thread_pool.ptr, - ) - output = [] - for i in range(top_k): - output.append((output_labels[i], output_scores[i])) - - return output - - @classmethod - def default_hyper_param(cls): - """Get default training hyper-parameters.""" - return lib.omikuji_default_hyper_param() - - @classmethod - def train_on_data( - cls, data_path: str, hyper_param=None, n_threads: Optional[int] = None - ): - """Train a model with the given data and hyper-parameters.""" - thread_pool = _ThreadPoolHandle(n_threads) - dataset_ptr = lib.load_omikuji_data_set( - ffi.new("char[]", data_path.encode()), thread_pool.ptr - ) - if dataset_ptr == ffi.NULL: - raise RuntimeError("Failed to load data from %s" % (data_path,)) - - dataset_ptr = ffi.gc(dataset_ptr, lib.free_omikuji_data_set) - - if hyper_param is None: - hyper_param = cls.default_hyper_param() - - model_ptr = lib.train_omikuji_model(dataset_ptr, hyper_param, thread_pool.ptr) - if model_ptr == ffi.NULL: - raise RuntimeError("Failed to train model") - - return Model(model_ptr) - - -def init_logger(): - """Initialize a simple logger that writes to stdout.""" - if lib.omikuji_init_logger() < 0: - raise RuntimeWarning("Failed to initialize logger") - - -ffi.init_once(init_logger, "omikuji_init_logger") +__all__ = [ + "Model", + "LossType", + "HyperParam", + "default_hyper_param", + "train_on_data", + "init_logger", +] + +# Import from PyO3 native module +from omikuji._omikuji import ( + Model, + LossType, + HyperParam, + default_hyper_param, + train_on_data, + init_logger, +) + +# Backward-compatible aliases for the old LossType member names +LossType.HINGE = LossType.Hinge +LossType.LOG = LossType.Log + +# Re-export for backward compatibility +__all__ += ["__version__"] diff --git a/python-wrapper/src/lib.rs b/python-wrapper/src/lib.rs new file mode 100644 index 0000000..9ecd93e --- /dev/null +++ b/python-wrapper/src/lib.rs @@ -0,0 +1,562 @@ +use itertools::Itertools; +use omikuji::rayon; +use pyo3::prelude::*; +use pyo3::types::PyDict; +use std::path::Path; +use std::sync::Mutex; + +/// PyO3-compatible Model wrapper +#[pyclass] +struct Model { + inner: omikuji::Model, + /// Cached thread pool for parallel operations (mirrors Python implementation's self._thread_pool) + thread_pool: Mutex, + /// Cached process ID for fork detection (mirrors Python implementation's self._pid) + cached_pid: usize, +} + +#[pymethods] +impl Model { + /// Load Omikuji model from the given directory. + #[staticmethod] + fn load(path: String) -> PyResult { + let model = omikuji::Model::load(Path::new(&path)).map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to load model: {}", e)) + })?; + + // Create default thread pool (rayon auto-detects thread count) + let pool = rayon::ThreadPoolBuilder::new() + .stack_size(32 * 1024 * 1024) + .build() + .map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!( + "Failed to create thread pool: {}", + e + )) + })?; + + Ok(Model { + inner: model, + thread_pool: Mutex::new(pool), + cached_pid: std::process::id() as usize, + }) + } + + /// Initialize/replace the thread pool for processing model predictions. + /// + /// If n_threads is set to 0, the number of threads is automatically chosen + /// based on the number of available CPU cores. + fn init_prediction_thread_pool(&mut self, n_threads: usize) -> PyResult<()> { + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(n_threads) + .stack_size(32 * 1024 * 1024) + .build() + .map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!( + "Failed to create thread pool: {}", + e + )) + })?; + + *self.thread_pool.lock().unwrap() = pool; + self.cached_pid = std::process::id() as usize; + Ok(()) + } + + /// Save Omikuji model to the given directory. + fn save(&self, path: String) -> PyResult<()> { + self.inner.save(Path::new(&path)).map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to save model: {}", e)) + })?; + Ok(()) + } + + /// Densify model weights to speed up prediction at the expense of memory usage. + #[pyo3(signature = (max_sparse_density=0.1, n_threads=None))] + fn densify_weights( + &mut self, + max_sparse_density: f32, + n_threads: Option, + ) -> PyResult<()> { + // Check for fork: rebuild thread pool if PID changed (mirrors Python fork detection) + let current_pid = std::process::id() as usize; + if current_pid != self.cached_pid { + let mut pool = self.thread_pool.lock().unwrap(); + *pool = rayon::ThreadPoolBuilder::new() + .stack_size(32 * 1024 * 1024) + .build() + .map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!( + "Failed to create thread pool: {}", + e + )) + })?; + self.cached_pid = current_pid; + } + + if let Some(n) = n_threads { + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(n) + .stack_size(32 * 1024 * 1024) + .build() + .map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!( + "Failed to create thread pool: {}", + e + )) + })?; + pool.install(|| self.inner.densify_weights(max_sparse_density)); + } else { + // Reuse the cached thread pool (mirrors Python implementation's self._thread_pool behavior) + let guard = self.thread_pool.lock().unwrap(); + guard.install(|| self.inner.densify_weights(max_sparse_density)); + } + Ok(()) + } + + /// Make predictions with Omikuji model. + #[pyo3(signature = (feature_value_pairs, beam_size=None, top_k=None))] + fn predict( + &mut self, + _py: Python, + mut feature_value_pairs: Vec<(u32, f32)>, + beam_size: Option, + top_k: Option, + ) -> PyResult> { + let beam_size = beam_size.unwrap_or(10); + let top_k = top_k.unwrap_or(10); + + // Sort by feature index (mirrors Python implementation's behavior) + feature_value_pairs.sort_by_key(|&(f, _)| f); + + // Validate indices are strictly ascending and in range (mirrors Python implementation's behavior) + let n_features = self.n_features(); + if !feature_value_pairs.is_empty() { + for ((f1, _), (f2, _)) in feature_value_pairs.iter().tuple_windows() { + if !(*f1 < *f2) { + return Err(pyo3::exceptions::PyValueError::new_err( + "Feature indices must be strictly ascending", + )); + } + } + let (first, _) = &feature_value_pairs[0]; + let (last, _) = &feature_value_pairs[feature_value_pairs.len() - 1]; + if *first >= n_features as u32 || *last >= n_features as u32 { + return Err(pyo3::exceptions::PyValueError::new_err( + "Feature index out of range", + )); + } + } + + // Check for fork: rebuild thread pool if PID changed (mirrors Python fork detection) + let current_pid = std::process::id() as usize; + if current_pid != self.cached_pid { + let mut pool = self.thread_pool.lock().unwrap(); + *pool = rayon::ThreadPoolBuilder::new() + .stack_size(32 * 1024 * 1024) + .build() + .map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!( + "Failed to create thread pool: {}", + e + )) + })?; + self.cached_pid = current_pid; + } + + // Use the cached thread pool for prediction (same as Python) + let pool = self.thread_pool.lock().unwrap(); + let predictions = pool.install(|| self.inner.predict(&feature_value_pairs, beam_size)); + + let result: Vec<(u32, f32)> = predictions + .into_iter() + .take(top_k) + .map(|(label, score)| (label, score)) + .collect(); + Ok(result) + } + + /// Get the expected dimension of feature vectors. + #[getter] + fn n_features(&self) -> usize { + self.inner.n_features() + } + + /// The number of trees in the forest model. + #[getter] + fn n_trees(&self) -> usize { + self.inner.n_trees() + } + + /// Get the default training hyper-parameters. + #[classmethod] + fn default_hyper_param(_cls: &Bound<'_, pyo3::types::PyType>) -> PyResult { + Ok(default_hyper_param()) + } + + /// Train a model with the given data file path and hyper-parameters. + #[classmethod] + #[pyo3(signature = (data_path, hyper_param=None, n_threads=None))] + fn train_on_data( + _cls: &Bound<'_, pyo3::types::PyType>, + _py: Python, + data_path: String, + hyper_param: Option<&HyperParam>, + n_threads: Option, + ) -> PyResult { + train_on_data(_py, data_path, hyper_param, n_threads) + } +} + +/// Loss type enum for linear classifiers +#[pyclass(eq, eq_int, from_py_object)] +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum LossType { + Hinge, + Log, +} + +#[pymethods] +impl LossType { + fn __repr__(&self) -> &'static str { + match self { + LossType::Hinge => "LossType.Hinge", + LossType::Log => "LossType.Log", + } + } +} + +impl From for LossType { + fn from(lt: omikuji::model::liblinear::LossType) -> Self { + match lt { + omikuji::model::liblinear::LossType::Hinge => LossType::Hinge, + omikuji::model::liblinear::LossType::Log => LossType::Log, + } + } +} + +impl From for omikuji::model::liblinear::LossType { + fn from(lt: LossType) -> Self { + match lt { + LossType::Hinge => omikuji::model::liblinear::LossType::Hinge, + LossType::Log => omikuji::model::liblinear::LossType::Log, + } + } +} + +/// Python-friendly HyperParam representation +#[pyclass(from_py_object)] +#[derive(Clone)] +struct HyperParam { + #[pyo3(get, set)] + n_trees: usize, + #[pyo3(get, set)] + min_branch_size: usize, + #[pyo3(get, set)] + max_depth: usize, + #[pyo3(get, set)] + centroid_threshold: f32, + #[pyo3(get, set)] + collapse_every_n_layers: usize, + #[pyo3(get, set)] + linear_loss_type: LossType, + #[pyo3(get, set)] + linear_eps: f32, + #[pyo3(get, set)] + linear_c: f32, + #[pyo3(get, set)] + linear_weight_threshold: f32, + #[pyo3(get, set)] + linear_max_iter: u32, + #[pyo3(get, set)] + cluster_k: usize, + #[pyo3(get, set)] + cluster_balanced: bool, + #[pyo3(get, set)] + cluster_eps: f32, + #[pyo3(get, set)] + cluster_min_size: usize, + #[pyo3(get, set)] + tree_structure_only: bool, + #[pyo3(get, set)] + train_trees_1_by_1: bool, +} + +#[pymethods] +impl HyperParam { + #[new] + #[pyo3(signature = ( + n_trees=None, + min_branch_size=None, + max_depth=None, + centroid_threshold=None, + collapse_every_n_layers=None, + linear_loss_type=None, + linear_eps=None, + linear_c=None, + linear_weight_threshold=None, + linear_max_iter=None, + cluster_k=None, + cluster_balanced=None, + cluster_eps=None, + cluster_min_size=None, + tree_structure_only=false, + train_trees_1_by_1=false, + **kwargs + ))] + fn new( + n_trees: Option, + min_branch_size: Option, + max_depth: Option, + centroid_threshold: Option, + collapse_every_n_layers: Option, + linear_loss_type: Option, + linear_eps: Option, + linear_c: Option, + linear_weight_threshold: Option, + linear_max_iter: Option, + cluster_k: Option, + cluster_balanced: Option, + cluster_eps: Option, + cluster_min_size: Option, + tree_structure_only: bool, + train_trees_1_by_1: bool, + kwargs: Option<&Bound<'_, PyDict>>, + ) -> PyResult { + let default = omikuji::model::train::HyperParam::default(); + + let mut hyper_param = Self { + n_trees: n_trees.unwrap_or(default.n_trees), + min_branch_size: min_branch_size.unwrap_or(default.min_branch_size), + max_depth: max_depth.unwrap_or(default.max_depth), + centroid_threshold: centroid_threshold.unwrap_or(default.centroid_threshold), + collapse_every_n_layers: collapse_every_n_layers + .unwrap_or(default.collapse_every_n_layers), + linear_loss_type: linear_loss_type.unwrap_or(default.linear.loss_type.into()), + linear_eps: linear_eps.unwrap_or(default.linear.eps), + linear_c: linear_c.unwrap_or(default.linear.c), + linear_weight_threshold: linear_weight_threshold + .unwrap_or(default.linear.weight_threshold), + linear_max_iter: linear_max_iter.unwrap_or(default.linear.max_iter), + cluster_k: cluster_k.unwrap_or(default.cluster.k), + cluster_balanced: cluster_balanced.unwrap_or(default.cluster.balanced), + cluster_eps: cluster_eps.unwrap_or(default.cluster.eps), + cluster_min_size: cluster_min_size.unwrap_or(default.cluster.min_size), + tree_structure_only, + train_trees_1_by_1, + }; + + // Allow dict-style passing for linear and cluster sub-params + if let Some(kwargs) = kwargs { + if let Some(linear) = kwargs.get_item("linear").ok().flatten() { + if let Ok(linear_dict) = linear.cast::() { + hyper_param.linear_eps = linear_dict + .get_item("eps") + .ok() + .flatten() + .and_then(|v| v.extract::().ok()) + .unwrap_or(hyper_param.linear_eps); + hyper_param.linear_c = linear_dict + .get_item("c") + .ok() + .flatten() + .and_then(|v| v.extract::().ok()) + .unwrap_or(hyper_param.linear_c); + hyper_param.linear_weight_threshold = linear_dict + .get_item("weight_threshold") + .ok() + .flatten() + .and_then(|v| v.extract::().ok()) + .unwrap_or(hyper_param.linear_weight_threshold); + hyper_param.linear_max_iter = linear_dict + .get_item("max_iter") + .ok() + .flatten() + .and_then(|v| v.extract::().ok()) + .unwrap_or(hyper_param.linear_max_iter); + if let Some(loss_type) = linear_dict + .get_item("loss_type") + .ok() + .flatten() + .and_then(|v| v.extract::().ok()) + { + hyper_param.linear_loss_type = loss_type; + } + } + } + if let Some(cluster) = kwargs.get_item("cluster").ok().flatten() { + if let Ok(cluster_dict) = cluster.cast::() { + hyper_param.cluster_k = cluster_dict + .get_item("k") + .ok() + .flatten() + .and_then(|v| v.extract::().ok()) + .unwrap_or(hyper_param.cluster_k); + hyper_param.cluster_balanced = cluster_dict + .get_item("balanced") + .ok() + .flatten() + .and_then(|v| v.extract::().ok()) + .unwrap_or(hyper_param.cluster_balanced); + hyper_param.cluster_eps = cluster_dict + .get_item("eps") + .ok() + .flatten() + .and_then(|v| v.extract::().ok()) + .unwrap_or(hyper_param.cluster_eps); + hyper_param.cluster_min_size = cluster_dict + .get_item("min_size") + .ok() + .flatten() + .and_then(|v| v.extract::().ok()) + .unwrap_or(hyper_param.cluster_min_size); + } + } + } + + Ok(hyper_param) + } + + fn __repr__(&self) -> String { + format!( + "HyperParam(n_trees={}, min_branch_size={}, max_depth={}, centroid_threshold={:.4})", + self.n_trees, self.min_branch_size, self.max_depth, self.centroid_threshold + ) + } +} + +/// Convert HyperParam to native type (internal use only) +fn hyper_param_to_native(hp: &HyperParam) -> omikuji::model::train::HyperParam { + omikuji::model::train::HyperParam { + n_trees: hp.n_trees, + min_branch_size: hp.min_branch_size, + max_depth: hp.max_depth, + centroid_threshold: hp.centroid_threshold, + collapse_every_n_layers: hp.collapse_every_n_layers, + linear: omikuji::model::liblinear::HyperParam { + loss_type: hp.linear_loss_type.into(), + eps: hp.linear_eps, + c: hp.linear_c, + weight_threshold: hp.linear_weight_threshold, + max_iter: hp.linear_max_iter, + }, + cluster: omikuji::model::cluster::HyperParam { + k: hp.cluster_k, + balanced: hp.cluster_balanced, + eps: hp.cluster_eps, + min_size: hp.cluster_min_size, + }, + tree_structure_only: hp.tree_structure_only, + train_trees_1_by_1: hp.train_trees_1_by_1, + } +} + +/// Get the default training hyper-parameters. +#[pyfunction] +fn default_hyper_param() -> HyperParam { + let default = omikuji::model::train::HyperParam::default(); + HyperParam { + n_trees: default.n_trees, + min_branch_size: default.min_branch_size, + max_depth: default.max_depth, + centroid_threshold: default.centroid_threshold, + collapse_every_n_layers: default.collapse_every_n_layers, + linear_loss_type: default.linear.loss_type.into(), + linear_eps: default.linear.eps, + linear_c: default.linear.c, + linear_weight_threshold: default.linear.weight_threshold, + linear_max_iter: default.linear.max_iter, + cluster_k: default.cluster.k, + cluster_balanced: default.cluster.balanced, + cluster_eps: default.cluster.eps, + cluster_min_size: default.cluster.min_size, + tree_structure_only: default.tree_structure_only, + train_trees_1_by_1: default.train_trees_1_by_1, + } +} + +/// Helper: create a default thread pool for a new Model +fn make_default_pool() -> PyResult { + rayon::ThreadPoolBuilder::new() + .stack_size(32 * 1024 * 1024) + .build() + .map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!( + "Failed to create thread pool: {}", + e + )) + }) +} + +/// Helper: construct a Model with a thread pool +fn make_model(inner: omikuji::Model) -> PyResult { + let pool = make_default_pool()?; + Ok(Model { + inner, + thread_pool: Mutex::new(pool), + cached_pid: std::process::id() as usize, + }) +} + +/// Train a model with the given data file path and hyper-parameters. +#[pyfunction] +#[pyo3(signature = (data_path, hyper_param=None, n_threads=None))] +fn train_on_data( + _py: Python, + data_path: String, + hyper_param: Option<&HyperParam>, + n_threads: Option, +) -> PyResult { + let hyper_param = match hyper_param { + Some(hp) => hyper_param_to_native(hp), + None => omikuji::model::train::HyperParam::default(), + }; + + if let Some(n) = n_threads { + let pool = rayon::ThreadPoolBuilder::new() + .num_threads(n) + .stack_size(32 * 1024 * 1024) + .build() + .map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!( + "Failed to create thread pool: {}", + e + )) + })?; + let dataset = pool.install(|| { + omikuji::DataSet::load_xc_repo_data_file(Path::new(&data_path)).map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to load data: {}", e)) + }) + })?; + let model = pool.install(|| hyper_param.train(dataset)); + make_model(model) + } else { + let dataset = + omikuji::DataSet::load_xc_repo_data_file(Path::new(&data_path)).map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to load data: {}", e)) + })?; + let model = hyper_param.train(dataset); + make_model(model) + } +} + +/// Initialize a simple logger that writes to stdout. +#[pyfunction] +fn init_logger() -> PyResult<()> { + simple_logger::init().map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to initialize logger: {}", e)) + })?; + Ok(()) +} + +/// Python module definition +#[pymodule] +fn _omikuji(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_function(wrap_pyfunction!(default_hyper_param, m)?)?; + m.add_function(wrap_pyfunction!(train_on_data, m)?)?; + m.add_function(wrap_pyfunction!(init_logger, m)?)?; + Ok(()) +} diff --git a/setup.py b/setup.py deleted file mode 100644 index 435767a..0000000 --- a/setup.py +++ /dev/null @@ -1,67 +0,0 @@ -from setuptools import setup -from os import path -import sys - - -# https://stackoverflow.com/a/65622116 ¯\_(ツ)_/¯ -if sys.platform in ["win32", "cygwin"]: - os.environ["DISTUTILS_USE_SDK"] = "1" - - -def build_native(spec): - build = spec.add_external_build(cmd=["cargo", "build", "--release"], path="c-api") - spec.add_cffi_module( - module_path="omikuji._libomikuji", - dylib=lambda: build.find_dylib("omikuji", in_path="target/release"), - header_filename=lambda: build.find_header( - "omikuji.h", in_path="target/include" - ), - rtld_flags=["NOW", "NODELETE"], - ) - - -def load_readme(): - curr_dir = path.abspath(path.dirname(__file__)) - with open(path.join(curr_dir, "README.md"), encoding="utf-8") as f: - return f.read() - - -setup( - name="omikuji", - version="0.5.1", - author="Tom Dong", - author_email="tom.tung.dyb@gmail.com", - description=( - "Python binding to Omikuji, an efficient implementation of Partioned Label " - "Trees and its variations for extreme multi-label classification" - ), - long_description=load_readme(), - long_description_content_type="text/markdown", - python_requires=">=3.8", - url="https://github.com/tomtung/omikuji", - license="MIT", - packages=["omikuji"], - package_dir={"": "python-wrapper"}, - zip_safe=False, - platforms="any", - setup_requires=["milksnake>=0.1.6"], - install_requires=["milksnake>=0.1.6"], - milksnake_tasks=[build_native], - milksnake_universal=False, - classifiers=[ - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Rust", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Topic :: Software Development :: Libraries", - ], -)