From 76f4a858a3e23f6a30d8d53759558662f7bb8be1 Mon Sep 17 00:00:00 2001 From: Bryan Hui Date: Wed, 25 Mar 2026 14:07:16 +0800 Subject: [PATCH] Release version 1.3.0 Major changes: - Add Python 3.12 support (dropped 3.7-3.11) - Add PyTorch 2.1.0, 2.1.1, 2.2.0 support (dropped < 2.1.0) - Fix critical logsignature segfault caused by GIL handling - Fix C++ ABI compatibility issues with std::unique_ptr - Update CI/CD to Ubuntu 22.04 and Windows Server 2022 Technical fixes: - Restructure make_lyndon_info to properly handle GIL - Replace std::unique_ptr with raw pointers in LyndonInfo - Implement Rule of 5 for proper memory management - Update build system for C++17 compatibility --- .github/workflows/build.yml | 60 ++-- .github/workflows/deploy.yml | 32 +- .github/workflows/log_dispatch.yml | 12 +- .github/workflows/test_deployed.yml | 18 +- .github/workflows_templates/from_template.py | 337 +++++++++---------- CHANGELOG.txt | 11 + README.rst | 8 +- docs/pages/usage/installation.rst | 6 +- metadata.py | 49 ++- setup.py | 113 ++++--- src/abi_stable_pointer.hpp | 122 +++++++ src/logsignature.cpp | 80 +++-- src/lyndon.cpp | 6 +- src/lyndon.hpp | 3 +- src/pycapsule.inl | 13 +- src/signatory/__init__.py | 63 ++-- 16 files changed, 556 insertions(+), 377 deletions(-) create mode 100644 src/abi_stable_pointer.hpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c99920e..11eb182 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,23 +25,23 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04] - python-version: [3.9.12] - pytorch-version: [1.11.0] + os: [ubuntu-22.04] + python-version: [3.12.0] + pytorch-version: [2.2.0] steps: - name: Checkout code - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/checkout@v1 - name: Install Python - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/setup-python@v1 with: - python-version: '3.7' + python-version: '3.12' - name: Check version - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) run: | python -c "import itertools as it import re @@ -70,23 +70,23 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04] - python-version: [3.9.12] - pytorch-version: [1.11.0] + os: [ubuntu-22.04] + python-version: [3.12.0] + pytorch-version: [2.2.0] steps: - name: Checkout code - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/checkout@v1 - name: Install Python - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/setup-python@v1 with: - python-version: '3.7' + python-version: '3.12' - name: Compare READMEs - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) run: | cp README.rst README.rst.old python command.py readme @@ -101,23 +101,23 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04] - python-version: [3.9.12] - pytorch-version: [1.11.0] + os: [ubuntu-22.04] + python-version: [3.12.0] + pytorch-version: [2.2.0] steps: - name: Checkout code - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/checkout@v1 - name: Install Python - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/setup-python@v1 with: - python-version: '3.7' + python-version: '3.12' - name: Compare Workflows - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) run: | cp .github/workflows/build.yml .github/workflows/build.yml.old cp .github/workflows/deploy.yml .github/workflows/deploy.yml.old @@ -142,24 +142,24 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-2016, ubuntu-18.04] - python-version: [3.7.0, 3.8.2, 3.9.12] - pytorch-version: [1.10.0, 1.10.1, 1.11.0] + os: [windows-2022, ubuntu-22.04] + python-version: [3.12.0] + pytorch-version: [2.1.0, 2.1.1, 2.2.0] fail-fast: false needs: [check_version, check_readme, check_workflows] steps: - name: Checkout code - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/checkout@v1 - name: Install Python - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/setup-python@v1 with: - python-version: '3.7' + python-version: '3.12' - name: Windows - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) && (matrix.os == 'windows-2016') + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) && (matrix.os == 'windows-2022') env: PYTHON_VERSION: ${{ matrix.python-version }} shell: cmd @@ -167,7 +167,7 @@ jobs: # It also means that if any command fails then the step as a whole should correctly # have a nonzero (fail) return code run: > - "C:/Program Files (x86)/Microsoft Visual Studio/2017/Enterprise/VC/Auxiliary/Build/vcvars64.bat" && + "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat" && SET DISTUTILS_USE_SDK=1 && %CONDA%/Scripts/conda create -n myenv python=%PYTHON_VERSION% -y && %CONDA%/Scripts/activate myenv && @@ -190,7 +190,7 @@ jobs: echo done - name: Linux - if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) && (matrix.os == 'ubuntu-18.04') + if: ((github.event_name == 'pull_request' && (true)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger -not-available- ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) && (matrix.os == 'ubuntu-22.04') env: PYTHON_VERSION: ${{ matrix.python-version }} # Deliberately only creating an sdist; see FAQ diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0584c79..135ab7e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -26,25 +26,25 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-18.04] - python-version: [3.9.12] - pytorch-version: [1.10.0, 1.10.1, 1.11.0] + os: [ubuntu-22.04] + python-version: [3.12.0] + pytorch-version: [2.1.0, 2.1.1, 2.2.0] steps: - name: Checkout code - if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/checkout@v1 with: ref: master - name: Install Python - if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/setup-python@v1 with: - python-version: '3.7' + python-version: '3.12' - name: Linux - if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) && (matrix.os == 'ubuntu-18.04') + if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) && (matrix.os == 'ubuntu-22.04') env: PYTHON_VERSION: ${{ matrix.python-version }} # Deliberately only creating an sdist; see FAQ @@ -66,26 +66,26 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-2016, ubuntu-18.04] - python-version: [3.7.0, 3.8.2, 3.9.12] - pytorch-version: [1.10.0, 1.10.1, 1.11.0] + os: [windows-2022, ubuntu-22.04] + python-version: [3.12.0] + pytorch-version: [2.1.0, 2.1.1, 2.2.0] fail-fast: false needs: [deploy_sdist] steps: - name: Checkout code - if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/checkout@v1 with: ref: master - name: Install Python - if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/setup-python@v1 with: - python-version: '3.7' + python-version: '3.12' - name: Windows - if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) && (matrix.os == 'windows-2016') + if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) && (matrix.os == 'windows-2022') env: PYTHON_VERSION: ${{ matrix.python-version }} shell: cmd @@ -93,7 +93,7 @@ jobs: # It also means that if any command fails then the step as a whole should correctly # have a nonzero (fail) return code run: > - "C:/Program Files (x86)/Microsoft Visual Studio/2017/Enterprise/VC/Auxiliary/Build/vcvars64.bat" && + "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat" && SET DISTUTILS_USE_SDK=1 && %CONDA%/Scripts/conda create -n myenv python=%PYTHON_VERSION% -y && %CONDA%/Scripts/activate myenv && @@ -126,7 +126,7 @@ jobs: echo done - name: Linux - if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) && (matrix.os == 'ubuntu-18.04') + if: ((github.event_name == 'pull_request' && (github.event.pull_request.merged)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger deploy ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) && (matrix.os == 'ubuntu-22.04') env: PYTHON_VERSION: ${{ matrix.python-version }} # Deliberately only creating an sdist; see FAQ diff --git a/.github/workflows/log_dispatch.yml b/.github/workflows/log_dispatch.yml index 185dc30..c94f39c 100644 --- a/.github/workflows/log_dispatch.yml +++ b/.github/workflows/log_dispatch.yml @@ -18,9 +18,9 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-2016, ubuntu-18.04] - python-version: [3.7.0, 3.8.2, 3.9.12] - pytorch-version: [1.10.0, 1.10.1, 1.11.0] + os: [windows-2022, ubuntu-22.04] + python-version: [3.12.0] + pytorch-version: [2.1.0, 2.1.1, 2.2.0] fail-fast: false steps: # Deliberately no 'if' statement: this will trigger on every repository_dispatch @@ -30,9 +30,9 @@ jobs: SIGNATORY_ACTION: ${{ github.event.action }} SIGNATORY_EVENT_NAME_CHECK: ${{ github.event_name == 'repository_dispatch' }} SIGNATORY_TRIGGER_CHECK: ${{ contains(github.event.action, '-trigger log_dispatch ') }} - SIGNATORY_OS_CHECK: ${{ ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) }} - SIGNATORY_PV_CHECK: ${{ ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')) }} - SIGNATORY_IF_CHECK: ${{ (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger log_dispatch ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *'))) }} + SIGNATORY_OS_CHECK: ${{ ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) }} + SIGNATORY_PV_CHECK: ${{ ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')) }} + SIGNATORY_IF_CHECK: ${{ (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger log_dispatch ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *'))) }} # Cross-platform way of printing out all these environment variables run: > python -c "import os; diff --git a/.github/workflows/test_deployed.yml b/.github/workflows/test_deployed.yml index 97898f5..12bb5a6 100644 --- a/.github/workflows/test_deployed.yml +++ b/.github/workflows/test_deployed.yml @@ -20,25 +20,25 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [windows-2016, ubuntu-18.04] - python-version: [3.7.0, 3.8.2, 3.9.12] - pytorch-version: [1.10.0, 1.10.1, 1.11.0] + os: [windows-2022, ubuntu-22.04] + python-version: [3.12.0] + pytorch-version: [2.1.0, 2.1.1, 2.2.0] fail-fast: false steps: - name: Checkout code - if: ((github.event_name == '-no-event-' && (false)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger test_deployed ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == '-no-event-' && (false)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger test_deployed ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/checkout@v1 with: ref: master - name: Install Python - if: ((github.event_name == '-no-event-' && (false)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger test_deployed ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) + if: ((github.event_name == '-no-event-' && (false)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger test_deployed ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) uses: actions/setup-python@v1 with: - python-version: '3.7' + python-version: '3.12' - name: Windows - if: ((github.event_name == '-no-event-' && (false)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger test_deployed ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) && (matrix.os == 'windows-2016') + if: ((github.event_name == '-no-event-' && (false)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger test_deployed ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) && (matrix.os == 'windows-2022') env: PYTHON_VERSION: ${{ matrix.python-version }} shell: cmd @@ -46,7 +46,7 @@ jobs: # It also means that if any command fails then the step as a whole should correctly # have a nonzero (fail) return code run: > - "C:/Program Files (x86)/Microsoft Visual Studio/2017/Enterprise/VC/Auxiliary/Build/vcvars64.bat" && + "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat" && SET DISTUTILS_USE_SDK=1 && %CONDA%/Scripts/conda create -n myenv python=%PYTHON_VERSION% -y && %CONDA%/Scripts/activate myenv && @@ -75,7 +75,7 @@ jobs: echo done - name: Linux - if: ((github.event_name == '-no-event-' && (false)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger test_deployed ') && ((contains(github.event.action, '-os windows-2016') && matrix.os == 'windows-2016') || (contains(github.event.action, '-os ubuntu-18.04') && matrix.os == 'ubuntu-18.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.7.0') && matrix.python-version == '3.7.0') || (contains(github.event.action, '-pv 3.8.2') && matrix.python-version == '3.8.2') || (contains(github.event.action, '-pv 3.9.12') && matrix.python-version == '3.9.12') || contains(github.event.action, '-pv *')))) && (matrix.os == 'ubuntu-18.04') + if: ((github.event_name == '-no-event-' && (false)) || (github.event_name == 'repository_dispatch' && contains(github.event.action, '-trigger test_deployed ') && ((contains(github.event.action, '-os windows-2022') && matrix.os == 'windows-2022') || (contains(github.event.action, '-os ubuntu-22.04') && matrix.os == 'ubuntu-22.04') || contains(github.event.action, '-os *')) && ((contains(github.event.action, '-pv 3.12.0') && matrix.python-version == '3.12.0') || contains(github.event.action, '-pv *')))) && (matrix.os == 'ubuntu-22.04') env: PYTHON_VERSION: ${{ matrix.python-version }} # Deliberately only creating an sdist; see FAQ diff --git a/.github/workflows_templates/from_template.py b/.github/workflows_templates/from_template.py index 92bf85c..35382e0 100644 --- a/.github/workflows_templates/from_template.py +++ b/.github/workflows_templates/from_template.py @@ -24,7 +24,6 @@ These are specified as arguments in the template file, as a file e,g, """ - import io import os import re @@ -36,26 +35,28 @@ def _substitute(filename, **subs): """Reads a file with name `filename`.template and creates a file called `filename` by substituting substitutions of the form `<>`. - + e.g. if subs={'example': 'some text'} then '<>' will become 'some text'. - + It's smart enough to add enough white space to every new extra line of the substituted text, to match the indentation of the substitution point. """ - print('Started templating', filename) + print("Started templating", filename) here = os.path.realpath(os.path.dirname(__file__)) - with io.open(os.path.join(here, filename + '.template'), encoding='utf-8', mode='r') as f: + with io.open( + os.path.join(here, filename + ".template"), encoding="utf-8", mode="r" + ) as f: template_lines = f.readlines() - print('Finding arguments') + print("Finding arguments") # Look for the header of the form: # # Arguments: # Then take every line after that that looks like it's specifying an argument. - argument_header_finder = re.compile('^ *# *Arguments: *$') - argument_finder = re.compile(r'^ *# *([-\w]+): *([-\.\w]+) *(#.*)?$') + argument_header_finder = re.compile("^ *# *Arguments: *$") + argument_finder = re.compile(r"^ *# *([-\w]+): *([-\.\w]+) *(#.*)?$") found_argument_header = False for line in template_lines: if argument_header_finder.match(line): @@ -67,20 +68,24 @@ def _substitute(filename, **subs): break # found all arguments argument_name = argument_match.group(1) argument_value = argument_match.group(2) - print('Found', argument_name, 'with value', argument_value) + print("Found", argument_name, "with value", argument_value) if argument_name in subs: - raise RuntimeError('Argument {} already in subs for filename {}'.format(argument_name, filename)) + raise RuntimeError( + "Argument {} already in subs for filename {}".format( + argument_name, filename + ) + ) subs[argument_name] = argument_value - print('Finished finding arguments') - template = ''.join(template_lines) + print("Finished finding arguments") + template = "".join(template_lines) del template_lines # Compile regular expressions for each substitution subs_re = {} for sub_key, sub_val_raw in subs.items(): - sub_key_bracket = '<<{}>>'.format(sub_key) - sub_re = re.compile(r'^.*{}'.format(sub_key_bracket), flags=re.MULTILINE) - sub_val_split = sub_val_raw.split('\n') + sub_key_bracket = "<<{}>>".format(sub_key) + sub_re = re.compile(r"^.*{}".format(sub_key_bracket), flags=re.MULTILINE) + sub_val_split = sub_val_raw.split("\n") subs_re[sub_key_bracket] = (sub_re, sub_val_split) while True: @@ -90,150 +95,138 @@ def _substitute(filename, **subs): if not searched: continue found = True - white_space_amount = searched.end() - searched.start() - len(sub_key_bracket) - white_space = ' ' * white_space_amount + white_space_amount = ( + searched.end() - searched.start() - len(sub_key_bracket) + ) + white_space = " " * white_space_amount # We actually replace the <> and all of the text preceding it on the line; it's just that we copy all # of the preceding text back as-is. # (Would also be fine to leave the preceding text alone, but this is slightly easier to code.) - first_characters = template[searched.start():searched.start() + white_space_amount] + first_characters = template[ + searched.start() : searched.start() + white_space_amount + ] white_sub_vals = [first_characters + sub_val_split[0]] - white_sub_vals.extend([white_space + sub_val for sub_val in sub_val_split[1:]]) - template = template[:searched.start()] + '\n'.join(white_sub_vals) + template[searched.end():] + white_sub_vals.extend( + [white_space + sub_val for sub_val in sub_val_split[1:]] + ) + template = ( + template[: searched.start()] + + "\n".join(white_sub_vals) + + template[searched.end() :] + ) if not found: break - unsubbed = re.compile(r'<<\w*>>') + unsubbed = re.compile(r"<<\w*>>") search = unsubbed.search(template) if search: - raise RuntimeError('Found unsubbed string {} in {}'.format(search.group(), filename)) - - template = '\n'.join(['################################################', - '### ###', - '### THIS FILE IS AUTOGENERATED. DO NOT EDIT. ###', - '### ###', - '################################################', - '', - template]) - with io.open(os.path.join(here, '..', 'workflows', filename), encoding='utf-8', mode='w') as f: + raise RuntimeError( + "Found unsubbed string {} in {}".format(search.group(), filename) + ) + + template = "\n".join( + [ + "################################################", + "### ###", + "### THIS FILE IS AUTOGENERATED. DO NOT EDIT. ###", + "### ###", + "################################################", + "", + template, + ] + ) + with io.open( + os.path.join(here, "..", "workflows", filename), encoding="utf-8", mode="w" + ) as f: f.write(template) - print('Finished templating', filename) + print("Finished templating", filename) # These are some common strings to substitute in global_subs = dict( - -# Names of operating systems as GitHub Actions specifies them -windows = "windows-2016", -linux = "ubuntu-18.04", - -# Run on repository_dispatch and precisely one other event -on = \ -"""on: + # Names of operating systems as GitHub Actions specifies them + windows="windows-2022", + linux="ubuntu-22.04", + # Run on repository_dispatch and precisely one other event + on="""on: repository_dispatch: <>:""", - -# Only run on repository_dispatch -on_rd = "on: repository_dispatch", - -# Versions of Python -py37 = '3.7.0', -py38 = '3.8.2', -py39 = '3.9.12', -py_all = '[<>, <>, <>]', - -# Versions of PyTorch -pytorch110 = '1.10.0', -pytorch1101 = '1.10.1', -pytorch111 = '1.11.0', -pytorch_all = '[<>, <>, <>]', - -# A strategy for every operating system and version of Python -# Note that every possible combination must be specified in action_os and action_pv to have repository_dispatch work -# correctly -strategy = \ -"""runs-on: ${{ matrix.os }} + # Only run on repository_dispatch + on_rd="on: repository_dispatch", + # Versions of Python + py312="3.12.0", + py_all="[<>]", + # Versions of PyTorch + pytorch210="2.1.0", + pytorch211="2.1.1", + pytorch220="2.2.0", + pytorch_all="[<>, <>, <>]", + # A strategy for every operating system and version of Python + # Note that every possible combination must be specified in action_os and action_pv to have repository_dispatch work + # correctly + strategy="""runs-on: ${{ matrix.os }} strategy: matrix: os: [<>, <>] python-version: <> pytorch-version: <> fail-fast: false""", - -# A single Linux strategy -strategy_single = \ -"""runs-on: ${{ matrix.os }} + # A single Linux strategy + strategy_single="""runs-on: ${{ matrix.os }} strategy: matrix: os: [<>] - python-version: [<>] - pytorch-version: [<>] + python-version: [<>] + pytorch-version: [<>] """, - -# A single Linux strategy except with all PyTorch versions -strategy_single_all_pytorch = \ -"""runs-on: ${{ matrix.os }} + # A single Linux strategy except with all PyTorch versions + strategy_single_all_pytorch="""runs-on: ${{ matrix.os }} strategy: matrix: os: [<>] - python-version: [<>] + python-version: [<>] pytorch-version: <> """, - -# Tests whether a repository_dispatch-triggered action is triggered at all -# Note that trigger is intended to have a space after it (used to distinguish similar triggers) -action_trigger = "contains(github.event.action, '-trigger <> ')", - -# Tests whether a repository_dispatch-triggered action is triggered, depending on operating system -# Yes, this is a little mad. Only way I could get it work though. It seems like things like matrix.os -# only resolve into strings under certain circumstances. -_action_os_windows = "(contains(github.event.action, '-os <>') && matrix.os == '<>')", -_action_os_linux = "(contains(github.event.action, '-os <>') && matrix.os == '<>')", -_action_os_star = "contains(github.event.action, '-os *')", -action_os = "(<<_action_os_windows>> || <<_action_os_linux>> || <<_action_os_star>>)", - -# Tests whether a repository_dispatch-triggered action is triggered, depending on Python version -_action_pv_37 = "(contains(github.event.action, '-pv <>') && matrix.python-version == '<>')", -_action_pv_38 = "(contains(github.event.action, '-pv <>') && matrix.python-version == '<>')", -_action_pv_39 = "(contains(github.event.action, '-pv <>') && matrix.python-version == '<>')", -_action_pv_star = "contains(github.event.action, '-pv *')", -action_pv = "(<<_action_pv_37>> || <<_action_pv_38>> || <<_action_pv_39>> || <<_action_pv_star>>)", - -# Tests whether a step is triggered via the normal event associated with the workflow -if_event = "(github.event_name == '<>' && (<>))", - -# Tests whether a step is triggered via repository_dispatch -if_repository_dispatch = "(github.event_name == 'repository_dispatch' && <> && <> && <>)", - -# A generic if statement that should be on every step -# Will trigger the step if the normal event_name is the reason the workflow is running, and the event_cond is met -# OR -# if repository_dispatch is the reason the workflow is running, and the trigger, os, and Python version all match -if_ = "if: (<> || <>)", - - -# A step to checkout Code -checkout_code = \ -"""name: Checkout code + # Tests whether a repository_dispatch-triggered action is triggered at all + # Note that trigger is intended to have a space after it (used to distinguish similar triggers) + action_trigger="contains(github.event.action, '-trigger <> ')", + # Tests whether a repository_dispatch-triggered action is triggered, depending on operating system + # Yes, this is a little mad. Only way I could get it work though. It seems like things like matrix.os + # only resolve into strings under certain circumstances. + _action_os_windows="(contains(github.event.action, '-os <>') && matrix.os == '<>')", + _action_os_linux="(contains(github.event.action, '-os <>') && matrix.os == '<>')", + _action_os_star="contains(github.event.action, '-os *')", + action_os="(<<_action_os_windows>> || <<_action_os_linux>> || <<_action_os_star>>)", + # Tests whether a repository_dispatch-triggered action is triggered, depending on Python version + _action_pv_312="(contains(github.event.action, '-pv <>') && matrix.python-version == '<>')", + _action_pv_star="contains(github.event.action, '-pv *')", + action_pv="(<<_action_pv_312>> || <<_action_pv_star>>)", + # Tests whether a step is triggered via the normal event associated with the workflow + if_event="(github.event_name == '<>' && (<>))", + # Tests whether a step is triggered via repository_dispatch + if_repository_dispatch="(github.event_name == 'repository_dispatch' && <> && <> && <>)", + # A generic if statement that should be on every step + # Will trigger the step if the normal event_name is the reason the workflow is running, and the event_cond is met + # OR + # if repository_dispatch is the reason the workflow is running, and the trigger, os, and Python version all match + if_="if: (<> || <>)", + # A step to checkout Code + checkout_code="""name: Checkout code <> uses: actions/checkout@v1""", - -# A step to install Python 3.7. NOTE THAT IT IS DELIBERATELY ONLY 3.7. -# For other versions of Python then please use conda. -# The reason for this is that the setup-python action does not support many of the possible patch versions of Python. -install_python= \ -"""name: Install Python + # A step to install Python 3.12. NOTE THAT IT IS DELIBERATELY ONLY 3.12. + # For other versions of Python then please use conda. + # The reason for this is that the setup-python action does not support many of the possible patch versions of Python. + install_python="""name: Install Python <> uses: actions/setup-python@v1 with: - python-version: '3.7'""", - -# Performs the necessary set-up for Windows. -# Needs the cpuonly package because Windows is a bit weird. (Specifically this seems necessary when using Python 3.8 or -# PyTorch 1.5.) I don't have a Windows machine with a GPU to test that this doesn't break anything, but compiling -# Signatory on Linux with cpuonly installed, will still allow for Signatory to run on the GPU, so this is probably fine. -setup_windows = \ -r"""name: Windows + python-version: '3.12'""", + # Performs the necessary set-up for Windows. + # Needs the cpuonly package because Windows is a bit weird. + # Signatory on Linux with cpuonly installed will still allow for Signatory to run on the GPU. + setup_windows=r"""name: Windows <> && (matrix.os == '<>') env: PYTHON_VERSION: ${{ matrix.python-version }} @@ -242,25 +235,20 @@ def _substitute(filename, **subs): # It also means that if any command fails then the step as a whole should correctly # have a nonzero (fail) return code run: > - "C:/Program Files (x86)/Microsoft Visual Studio/2017/Enterprise/VC/Auxiliary/Build/vcvars64.bat" && + "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Auxiliary/Build/vcvars64.bat" && SET DISTUTILS_USE_SDK=1 && %CONDA%/Scripts/conda create -n myenv python=%PYTHON_VERSION% -y && %CONDA%/Scripts/activate myenv && python -m pip install --upgrade pip && conda install pytorch==${{ matrix.pytorch-version }} cpuonly -c pytorch -y && python command.py should_not_import &&""", - -# Builds a bdist_wheel on Windows -build_windows = \ -""" python setup.py egg_info --tag-build=".${{ matrix.pytorch-version }}" bdist_wheel && + # Builds a bdist_wheel on Windows + build_windows=""" python setup.py egg_info --tag-build=".${{ matrix.pytorch-version }}" bdist_wheel && python command.py should_not_import &&""", - -# Install from sdist or bdist_wheel on Windows -install_local_windows = ' for %%f in (./dist/*) do (python -m pip install ./dist/%%~nxf) &&', - -# Install from PyPI on Windows -install_remote_windows = \ -""" python -c "import subprocess; + # Install from sdist or bdist_wheel on Windows + install_local_windows=" for %%f in (./dist/*) do (python -m pip install ./dist/%%~nxf) &&", + # Install from PyPI on Windows + install_remote_windows=""" python -c "import subprocess; import sys; import time; import metadata; @@ -269,10 +257,8 @@ def _substitute(filename, **subs): ret = retry(lambda: not subprocess.run('python -m pip install <>signatory==' + metadata.version + '.${{ matrix.pytorch-version }} --only-binary signatory').returncode); sys.exit(not ret) " &&""", - -# Runs tests on Windows -test_windows = \ -r""" python -m pip install numpy && + # Runs tests on Windows + test_windows=r""" python -m pip install numpy && python -m pip install iisignature pytest && python -c "import os; import subprocess; @@ -282,13 +268,10 @@ def _substitute(filename, **subs): returncode_version = sys.version[:5] != os.environ['PYTHON_VERSION'][:5]; sys.exit(max(returncode_test, returncode_version)) " &&""", - -# Terminates a string of commands on Windows -terminate_windows = " echo done", - -# Performs setup for running on Linux -setup_linux = \ -r"""name: Linux + # Terminates a string of commands on Windows + terminate_windows=" echo done", + # Performs setup for running on Linux + setup_linux=r"""name: Linux <> && (matrix.os == '<>') env: PYTHON_VERSION: ${{ matrix.python-version }} @@ -301,30 +284,22 @@ def _substitute(filename, **subs): python -m pip install --upgrade pip conda install pytorch==${{ matrix.pytorch-version }} -c pytorch -y python command.py should_not_import""", - -# 'Builds' on Linux -build_linux = \ -""" python setup.py egg_info --tag-build=".${{ matrix.pytorch-version }}" sdist + # 'Builds' on Linux + build_linux=""" python setup.py egg_info --tag-build=".${{ matrix.pytorch-version }}" sdist python command.py should_not_import""", - -# Install from sdist or bdist_wheel on Linux -install_local_linux = \ -""" SIGNATORY_INSTALLED=$(python -c \"import os + # Install from sdist or bdist_wheel on Linux + install_local_linux=""" SIGNATORY_INSTALLED=$(python -c \"import os import sys x = os.listdir('dist') print(x[0]) sys.exit(len(x) != 1)\") python -m pip install ./dist/$SIGNATORY_INSTALLED""", - -# Install from PyPI on Linux -install_remote_linux = \ -""" retry () { $* || (sleep 20 && $*) || (sleep 40 && $*) || (sleep 120 && $*) || (sleep 240 && $*); } + # Install from PyPI on Linux + install_remote_linux=""" retry () { $* || (sleep 20 && $*) || (sleep 40 && $*) || (sleep 120 && $*) || (sleep 240 && $*); } SIGNATORY_VERSION=$(python -c "import metadata; print(metadata.version)") retry python -m pip install <>signatory==$SIGNATORY_VERSION.${{ matrix.pytorch-version }} --no-binary signatory""", - -# Runs tests on Linux -test_linux = \ -r""" python -m pip install numpy + # Runs tests on Linux + test_linux=r""" python -m pip install numpy python -m pip install iisignature pytest python -c "import os import subprocess @@ -334,38 +309,34 @@ def _substitute(filename, **subs): returncode_version = sys.version[:5] != os.environ['PYTHON_VERSION'][:5] sys.exit(max(returncode_test, returncode_version)) " """, - -# Terminates a string of commands on Linux (not actually necessary, -# but we use it for consistency with the other two OS) -terminate_linux = "", - -# Uploads dist/* to PyPI for Windows -upload_windows = \ -r""" pip install twine && + # Terminates a string of commands on Linux (not actually necessary, + # but we use it for consistency with the other two OS) + terminate_linux="", + # Uploads dist/* to PyPI for Windows + upload_windows=r""" pip install twine && twine upload -u patrick-kidger -p ${{ secrets.pypi_password }} <>dist/* &&""", - -# Uploads dist/* to PyPI for Unix -upload_unix = \ -""" pip install twine + # Uploads dist/* to PyPI for Unix + upload_unix=""" pip install twine twine upload -u patrick-kidger -p ${{ secrets.pypi_password }} <>dist/*""", ) # end of global_subs -global_subs['upload_linux'] = global_subs['upload_unix'] +global_subs["upload_linux"] = global_subs["upload_unix"] test = False if test: - global_subs['install_extras'] = '--index-url https://test.pypi.org/simple/ ' - global_subs['upload_extras'] = '--repository-url https://test.pypi.org/legacy/ ' + global_subs["install_extras"] = "--index-url https://test.pypi.org/simple/ " + global_subs["upload_extras"] = "--repository-url https://test.pypi.org/legacy/ " else: - global_subs['install_extras'] = '' - global_subs['upload_extras'] = '' + global_subs["install_extras"] = "" + global_subs["upload_extras"] = "" + def main(): """Make all templates.""" - _substitute('build.yml', **global_subs) - _substitute('deploy.yml', **global_subs) - _substitute('test_deployed.yml', **global_subs) - _substitute('log_dispatch.yml', **global_subs) + _substitute("build.yml", **global_subs) + _substitute("deploy.yml", **global_subs) + _substitute("test_deployed.yml", **global_subs) + _substitute("log_dispatch.yml", **global_subs) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7bc7edb..af5f9cb 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,14 @@ +1.3.0 +----- +Added support for Python 3.12 +Added support for PyTorch 2.1.0, 2.1.1, 2.2.0 +Dropped support for Python 3.7, 3.8, 3.9, 3.10, 3.11 +Dropped support for PyTorch < 2.1.0 +Updated CI to use Ubuntu 22.04 and Windows Server 2022 +Fixed C++ ABI compatibility issues with std::unique_ptr +Fixed GIL handling in make_lyndon_info to prevent segfaults +Updated build system for better C++17 compatibility + 1.2.7 ----- Added support for PyTorch 1.10.0, 1.10.1, 1.11.0 diff --git a/README.rst b/README.rst index 22a87ec..fba4406 100644 --- a/README.rst +++ b/README.rst @@ -28,9 +28,9 @@ Installation pip install signatory==. --no-cache-dir --force-reinstall -where ```` is the version of Signatory you would like to download (the most recent version is 1.2.7) and ```` is the version of PyTorch you are using. +where ```` is the version of Signatory you would like to download (the most recent version is 1.3.0) and ```` is the version of PyTorch you are using. -Available for Python 3.7--3.9 on Linux and Windows. Requires `PyTorch `__ 1.8.0--1.11.0. +Available for Python 3.12 on Linux and Windows. Requires `PyTorch `__ 2.1.0+. (If you need it, then previous versions of Signatory included support for older versions of Python, PyTorch, and MacOS, see `here `__.) @@ -41,11 +41,11 @@ Take care **not** to run ``pip install signatory``, as this will likely download Example: -------- -For example, if you are using PyTorch 1.11.0 and want Signatory 1.2.7, then you should run: +For example, if you are using PyTorch 2.2.0 and want Signatory 1.3.0, then you should run: .. code-block:: bash - pip install signatory==1.2.7.1.11.0 --no-cache-dir --force-reinstall + pip install signatory==1.3.0.2.2.0 --no-cache-dir --force-reinstall Why you need to specify all of this: ------------------------------------ diff --git a/docs/pages/usage/installation.rst b/docs/pages/usage/installation.rst index 2523a9d..e5a089e 100644 --- a/docs/pages/usage/installation.rst +++ b/docs/pages/usage/installation.rst @@ -9,7 +9,7 @@ Installation where ```` is the version of Signatory you would like to download (the most recent version is |version|) and ```` is the version of PyTorch you are using. -Available for Python 3.7--3.9 on Linux and Windows. Requires `PyTorch `__ 1.8.0--1.11.0. +Available for Python 3.12 on Linux and Windows. Requires `PyTorch `__ 2.1.0+. (If you need it, then previous versions of Signatory included support for older versions of Python, PyTorch, and MacOS, see `here `__.) @@ -20,11 +20,11 @@ Take care **not** to run ``pip install signatory``, as this will likely download Example: -------- -For example, if you are using PyTorch 1.11.0 and want Signatory 1.2.7, then you should run: +For example, if you are using PyTorch 2.2.0 and want Signatory 1.3.0, then you should run: .. code-block:: bash - pip install signatory==1.2.7.1.11.0 --no-cache-dir --force-reinstall + pip install signatory==1.3.0.2.2.0 --no-cache-dir --force-reinstall Why you need to specify all of this: ------------------------------------ diff --git a/metadata.py b/metadata.py index 0e62d52..0112653 100644 --- a/metadata.py +++ b/metadata.py @@ -3,9 +3,9 @@ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,49 +14,48 @@ # ========================================================================= """The metadata for the project.""" - import io import os import re #### DO NOT IMPORT NON-(STANDARD LIBRARY) MODULES HERE -project = 'signatory' +project = "signatory" author = "Patrick Kidger" copyright = "2019, {}".format(author) author_email = "contact@kidger.site" url = "https://github.com/patrick-kidger/signatory" license = "Apache-2.0" -python_requires = "~=3.6" +python_requires = ">=3.12" keywords = "signature" -classifiers = ["Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Intended Audience :: Financial and Insurance Industry", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: Apache Software License", - "Natural Language :: English", - "Operating System :: MacOS :: MacOS X", - "Operating System :: Microsoft :: Windows", - "Operating System :: Unix", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Topic :: Scientific/Engineering :: Information Analysis", - "Topic :: Scientific/Engineering :: Mathematics"] - -description = 'Differentiable computations of the signature and logsignature transforms, on both CPU and GPU.' +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Financial and Insurance Industry", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: Apache Software License", + "Natural Language :: English", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: Unix", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Scientific/Engineering :: Information Analysis", + "Topic :: Scientific/Engineering :: Mathematics", +] + +description = "Differentiable computations of the signature and logsignature transforms, on both CPU and GPU." here = os.path.realpath(os.path.dirname(__file__)) # for simplicity we actually store the version in the __version__ attribute in the source -with io.open(os.path.join(here, 'src', project, '__init__.py')) as f: +with io.open(os.path.join(here, "src", project, "__init__.py")) as f: meta_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", f.read(), re.M) if meta_match: version = meta_match.group(1) else: raise RuntimeError("Unable to find __version__ string.") -with io.open(os.path.join(here, 'README.rst'), 'r', encoding='utf-8') as f: +with io.open(os.path.join(here, "README.rst"), "r", encoding="utf-8") as f: readme = f.read() diff --git a/setup.py b/setup.py index 0047827..d32c141 100644 --- a/setup.py +++ b/setup.py @@ -3,9 +3,9 @@ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,58 +14,87 @@ # ========================================================================= """setup.py - hopefully you know what this does without me telling you...""" - import setuptools import sys + try: import torch.utils.cpp_extension as cpp except ImportError: - raise ImportError("PyTorch is not installed, and must be installed prior to installing Signatory.") - + raise ImportError( + "PyTorch is not installed, and must be installed prior to installing Signatory." + ) + import metadata extra_compile_args = [] # fvisibility flag because of https://pybind11.readthedocs.io/en/stable/faq.html#someclass-declared-with-greater-visibility-than-the-type-of-its-field-someclass-member-wattributes -if not sys.platform.startswith('win'): # linux or mac - extra_compile_args.append('-fvisibility=hidden') +if not sys.platform.startswith("win"): # linux or mac + extra_compile_args.append("-fvisibility=hidden") -if sys.platform.startswith('win'): # windows - extra_compile_args.append('/openmp') +if sys.platform.startswith("win"): # windows + extra_compile_args.append("/openmp") else: # linux or mac - extra_compile_args.append('-fopenmp') + extra_compile_args.append("-fopenmp") + + +# Custom BuildExtension to use new C++ ABI for compatibility with newer GCC +class BuildExtension(cpp.BuildExtension): + def build_extensions(self): + # Replace -D_GLIBCXX_USE_CXX11_ABI=0 with -D_GLIBCXX_USE_CXX11_ABI=1 + # The old ABI causes segfaults with GCC 11+ and newer PyTorch versions + for ext in self.extensions: + if hasattr(ext, "extra_compile_args"): + ext.extra_compile_args = [ + "-D_GLIBCXX_USE_CXX11_ABI=1" + if arg == "-D_GLIBCXX_USE_CXX11_ABI=0" + else arg + for arg in ext.extra_compile_args + ] + super().build_extensions() + -ext_modules = [cpp.CppExtension(name='_impl', - sources=['src/logsignature.cpp', - 'src/lyndon.cpp', - 'src/misc.cpp', - 'src/pytorchbind.cpp', - 'src/signature.cpp', - 'src/tensor_algebra_ops.cpp'], - depends=['src/logsignature.hpp', - 'src/lyndon.hpp', - 'src/misc.hpp', - 'src/signature.hpp', - 'src/tensor_algebra_ops.hpp'], - extra_compile_args=extra_compile_args)] +ext_modules = [ + cpp.CppExtension( + name="_impl", + sources=[ + "src/logsignature.cpp", + "src/lyndon.cpp", + "src/misc.cpp", + "src/pytorchbind.cpp", + "src/signature.cpp", + "src/tensor_algebra_ops.cpp", + ], + depends=[ + "src/logsignature.hpp", + "src/lyndon.hpp", + "src/misc.hpp", + "src/signature.hpp", + "src/tensor_algebra_ops.hpp", + ], + extra_compile_args=extra_compile_args, + ) +] -setuptools.setup(name=metadata.project, - version=metadata.version, - author=metadata.author, - author_email=metadata.author_email, - maintainer=metadata.author, - maintainer_email=metadata.author_email, - description=metadata.description, - long_description=metadata.readme, - url=metadata.url, - license=metadata.license, - keywords=metadata.keywords, - classifiers=metadata.classifiers, - zip_safe=False, - python_requires=metadata.python_requires, - packages=[metadata.project], - ext_package=metadata.project, - package_dir={'': 'src'}, - ext_modules=ext_modules, - cmdclass={'build_ext': cpp.BuildExtension}) +setuptools.setup( + name=metadata.project, + version=metadata.version, + author=metadata.author, + author_email=metadata.author_email, + maintainer=metadata.author, + maintainer_email=metadata.author_email, + description=metadata.description, + long_description=metadata.readme, + url=metadata.url, + license=metadata.license, + keywords=metadata.keywords, + classifiers=metadata.classifiers, + zip_safe=False, + python_requires=metadata.python_requires, + packages=[metadata.project], + ext_package=metadata.project, + package_dir={"": "src"}, + ext_modules=ext_modules, + cmdclass={"build_ext": BuildExtension}, +) diff --git a/src/abi_stable_pointer.hpp b/src/abi_stable_pointer.hpp new file mode 100644 index 0000000..621660c --- /dev/null +++ b/src/abi_stable_pointer.hpp @@ -0,0 +1,122 @@ +/* Copyright 2019 Patrick Kidger. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================================= */ + // ABI-stable smart pointer implementation to avoid C++ ABI compatibility issues + // with std::unique_ptr between different compiler versions and C++ ABI settings. + +#ifndef SIGNATORY_ABI_STABLE_POINTER_HPP +#define SIGNATORY_ABI_STABLE_POINTER_HPP + +#include // std::nullptr_t +#include // std::forward + +namespace signatory { + namespace misc { + // A simple, ABI-stable smart pointer with exclusive ownership semantics. + // Similar to std::unique_ptr but uses only raw pointers internally, + // making it immune to C++ ABI version mismatches. + // + // Key features: + // - Move-only (no copy constructor/assignment) + // - Automatic memory management via RAII + // - Compatible with both old and new C++ ABIs + // - Header-only implementation + template + class AbiStablePtr { + public: + // Default constructor - null pointer + constexpr AbiStablePtr() noexcept : ptr_(nullptr) {} + + // Construct from raw pointer (takes ownership) + explicit AbiStablePtr(T* p) noexcept : ptr_(p) {} + + // Destructor - automatically deletes owned object + ~AbiStablePtr() { + delete ptr_; + } + + // Disable copy operations (exclusive ownership) + AbiStablePtr(const AbiStablePtr&) = delete; + AbiStablePtr& operator=(const AbiStablePtr&) = delete; + + // Enable move operations + AbiStablePtr(AbiStablePtr&& other) noexcept : ptr_(other.ptr_) { + other.ptr_ = nullptr; // Transfer ownership + } + + AbiStablePtr& operator=(AbiStablePtr&& other) noexcept { + if (this != &other) { + delete ptr_; // Delete current object + ptr_ = other.ptr_; // Take ownership + other.ptr_ = nullptr; + } + return *this; + } + + // Reset - delete current object and take ownership of new one + void reset(T* p = nullptr) noexcept { + T* old = ptr_; + ptr_ = p; + delete old; + } + + // Release - return raw pointer without deleting (transfers ownership to caller) + T* release() noexcept { + T* p = ptr_; + ptr_ = nullptr; + return p; + } + + // Get raw pointer (doesn't transfer ownership) + T* get() const noexcept { + return ptr_; + } + + // Dereference operators + T& operator*() const { + return *ptr_; + } + + T* operator->() const { + return ptr_; + } + + // Boolean conversion + explicit operator bool() const noexcept { + return ptr_ != nullptr; + } + + // Comparison with nullptr + bool operator==(std::nullptr_t) const noexcept { + return ptr_ == nullptr; + } + + bool operator!=(std::nullptr_t) const noexcept { + return ptr_ != nullptr; + } + + private: + T* ptr_; // Raw pointer - ABI stable + }; + + // Helper function for creating AbiStablePtr (similar to std::make_unique) + template + AbiStablePtr make_abi_stable(Args&&... args) { + return AbiStablePtr(new T(std::forward(args)...)); + } + + } // namespace signatory::misc +} // namespace signatory + +#endif // SIGNATORY_ABI_STABLE_POINTER_HPP diff --git a/src/logsignature.cpp b/src/logsignature.cpp index 4a39c12..0e52771 100644 --- a/src/logsignature.cpp +++ b/src/logsignature.cpp @@ -16,7 +16,6 @@ #include #include // int64_t -#include // std::unique_ptr #include #include // std::invalid_argument #include // std::tie, std::tuple @@ -38,16 +37,47 @@ namespace signatory { // logsignature transformation just once, so that repeated use of the logsignature transformation is more // efficient. struct LyndonInfo { - LyndonInfo(std::unique_ptr lyndon_words, + // Use raw pointer with explicit ownership to avoid C++ ABI issues + // with std::unique_ptr between different compiler versions + LyndonInfo(lyndon::LyndonWords* lyndon_words_ptr, std::vector>>&& transforms, std::vector>>&& transforms_backward) : - lyndon_words{std::move(lyndon_words)}, - transforms{transforms}, - transforms_backward{transforms_backward} - {}; + lyndon_words(lyndon_words_ptr), + transforms(std::move(transforms)), + transforms_backward(std::move(transforms_backward)) + { + }; + + // Destructor to clean up the raw pointer + ~LyndonInfo() { + delete lyndon_words; + } + + // Disable copy to prevent double-free + LyndonInfo(const LyndonInfo&) = delete; + LyndonInfo& operator=(const LyndonInfo&) = delete; - // A list of Lyndon words - std::unique_ptr lyndon_words; + // Enable move semantics + LyndonInfo(LyndonInfo&& other) noexcept : + lyndon_words(other.lyndon_words), + transforms(std::move(other.transforms)), + transforms_backward(std::move(other.transforms_backward)) { + other.lyndon_words = nullptr; + } + + LyndonInfo& operator=(LyndonInfo&& other) noexcept { + if (this != &other) { + delete lyndon_words; + lyndon_words = other.lyndon_words; + transforms = std::move(other.transforms); + transforms_backward = std::move(other.transforms_backward); + other.lyndon_words = nullptr; + } + return *this; + } + + // A list of Lyndon words - raw pointer with manual memory management + lyndon::LyndonWords* lyndon_words; // The transforms for going from Lyndon words to Lyndon basis // This is in terms of the 'compressed' index, i.e. in the free Lie algebra @@ -59,7 +89,7 @@ namespace signatory { // They are grouped (the outermost vector) by anagram class std::vector>> transforms_backward; - constexpr static auto capsule_name = "signatory.LyndonInfoCapsule"; + static constexpr const char* capsule_name = "signatory.LyndonInfoCapsule"; }; // Compresses a representation of a member of the free Lie algebra. @@ -149,27 +179,31 @@ namespace signatory { } // namespace signatory::logsignature py::object make_lyndon_info(int64_t channels, s_size_type depth, LogSignatureMode mode) { + misc::checkargs_channels_depth(channels, depth); - py::gil_scoped_release release; - - std::unique_ptr lyndon_words; + lyndon::LyndonWords* lyndon_words = nullptr; std::vector>> transforms; std::vector>> transforms_backward; + + { + py::gil_scoped_release release; - // no make_unique in C++11 - if (mode == LogSignatureMode::Words) { - lyndon_words.reset(new lyndon::LyndonWords(channels, depth, lyndon::LyndonWords::word_tag)); - } - else if (mode == LogSignatureMode::Brackets) { - lyndon_words.reset(new lyndon::LyndonWords(channels, depth, lyndon::LyndonWords::bracket_tag)); - lyndon_words->to_lyndon_basis(transforms, transforms_backward); - lyndon_words->delete_extra(); - } - - return misc::wrap_capsule(std::move(lyndon_words), + if (mode == LogSignatureMode::Words) { + lyndon_words = new lyndon::LyndonWords(channels, depth, lyndon::LyndonWords::word_tag); + } + else if (mode == LogSignatureMode::Brackets) { + lyndon_words = new lyndon::LyndonWords(channels, depth, lyndon::LyndonWords::bracket_tag); + lyndon_words->to_lyndon_basis(transforms, transforms_backward); + lyndon_words->delete_extra(); + } + + } // GIL reacquired here when release goes out of scope + + py::object result = misc::wrap_capsule(lyndon_words, std::move(transforms), std::move(transforms_backward)); + return result; } std::tuple diff --git a/src/lyndon.cpp b/src/lyndon.cpp index f121382..d95516f 100644 --- a/src/lyndon.cpp +++ b/src/lyndon.cpp @@ -272,7 +272,8 @@ namespace signatory { void LyndonWords::delete_extra() { for (auto& depth_class : (*this)) { for (auto& lyndon_word : depth_class) { - lyndon_word.extra.reset(); + delete lyndon_word.extra; + lyndon_word.extra = nullptr; } } } @@ -351,8 +352,7 @@ namespace signatory { // generated. if (extra_) { - // no make_unique in C++11 - extra.reset(new LyndonWord::ExtraLyndonInformation(word, first_child, second_child)); + extra = new LyndonWord::ExtraLyndonInformation(word, first_child, second_child); } } } diff --git a/src/lyndon.hpp b/src/lyndon.hpp index 287a2a4..6c711d7 100644 --- a/src/lyndon.hpp +++ b/src/lyndon.hpp @@ -20,7 +20,6 @@ #include // int64_t - namespace signatory { namespace lyndon { struct LyndonWord; @@ -124,7 +123,7 @@ namespace signatory { // The index of this element in the sequence of all words (not necessarily Lyndon). int64_t tensor_algebra_index {0}; - std::unique_ptr extra {nullptr}; + ExtraLyndonInformation* extra = nullptr; friend struct LyndonWords; private: diff --git a/src/pycapsule.inl b/src/pycapsule.inl index c186eec..3e936b9 100644 --- a/src/pycapsule.inl +++ b/src/pycapsule.inl @@ -24,13 +24,18 @@ namespace signatory { namespace misc { template inline py::object wrap_capsule(Args&&... args) { - return py::reinterpret_steal(PyCapsule_New(new T{std::forward(args)...}, - T::capsule_name, - detail::CapsuleDestructor)); + T* obj = new T{std::forward(args)...}; + PyObject* capsule = PyCapsule_New(obj, T::capsule_name, detail::CapsuleDestructor); + if (!capsule) { + delete obj; + throw std::runtime_error("Failed to create capsule"); + } + return py::reinterpret_steal(capsule); } template inline T* unwrap_capsule(py::object capsule) { - return static_cast(PyCapsule_GetPointer(capsule.ptr(), T::capsule_name)); + T* ptr = static_cast(PyCapsule_GetPointer(capsule.ptr(), T::capsule_name)); + return ptr; } } /* namespace signatory::misc */ } // namespace signatory \ No newline at end of file diff --git a/src/signatory/__init__.py b/src/signatory/__init__.py index ea3d740..63c003f 100644 --- a/src/signatory/__init__.py +++ b/src/signatory/__init__.py @@ -3,9 +3,9 @@ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,45 +18,54 @@ Documentation: https://signatory.readthedocs.io """ - import torch # must be imported before anything from signatory try: from . import impl except ImportError as e: - if 'specified procedure could not be found' in str(e): - raise ImportError('Caught ImportError:\n```\n{}\n```\nThis can probably be fixed by updating your version of ' - 'Python, e.g. from 3.6.6 to 3.6.9. See the FAQ in the documentation.'.format(str(e))) - elif 'Symbol not found' in str(e): - raise ImportError('Caught Import Error:\n```\n{}\n```\nThis can probably be fixed by changing your version of ' - 'PyTorch. See the FAQ in the documentation.'.format(str(e))) + if "specified procedure could not be found" in str(e): + raise ImportError( + "Caught ImportError:\n```\n{}\n```\nThis can probably be fixed by updating your version of " + "Python, e.g. from 3.6.6 to 3.6.9. See the FAQ in the documentation.".format( + str(e) + ) + ) + elif "Symbol not found" in str(e): + raise ImportError( + "Caught Import Error:\n```\n{}\n```\nThis can probably be fixed by changing your version of " + "PyTorch. See the FAQ in the documentation.".format(str(e)) + ) else: raise from .augment import Augment from .deprecated import max_parallelism -from .logsignature_module import (signature_to_logsignature, - SignatureToLogSignature, - SignatureToLogsignature, - logsignature, - LogSignature, - Logsignature, # alias for LogSignature - logsignature_channels) +from .logsignature_module import ( + signature_to_logsignature, + SignatureToLogSignature, + SignatureToLogsignature, + logsignature, + LogSignature, + Logsignature, # alias for LogSignature + logsignature_channels, +) from .path import Path -from .signature_module import (signature, - Signature, - signature_channels, - extract_signature_term, - signature_combine, - multi_signature_combine) +from .signature_module import ( + signature, + Signature, + signature_channels, + extract_signature_term, + signature_combine, + multi_signature_combine, +) from .signature_inversion_module import invert_signature -from . import unstable # make it available as an attribute here, but don't import any unstable objects themselves -from .utility import (lyndon_words, - lyndon_brackets, - all_words) +from . import ( + unstable, +) # make it available as an attribute here, but don't import any unstable objects themselves +from .utility import lyndon_words, lyndon_brackets, all_words -__version__ = "1.2.7" +__version__ = "1.3.0" del torch