diff --git a/.github/workflows/build-with-clang.yml b/.github/workflows/build-with-clang.yml index 15d15578..87478d91 100644 --- a/.github/workflows/build-with-clang.yml +++ b/.github/workflows/build-with-clang.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] numpy_version: ["'numpy>=2'"] env: ONEAPI_ROOT: /opt/intel/oneapi @@ -55,7 +55,7 @@ jobs: - name: Install mkl_random dependencies run: | - pip install meson-python ninja cython cmake + pip install meson-python ninja "cython>=3.1.0" cmake pip install ${{ matrix.numpy_version }} - name: List oneAPI folder content diff --git a/.github/workflows/build-with-standard-clang.yml b/.github/workflows/build-with-standard-clang.yml index 2a903b50..c2eed53e 100644 --- a/.github/workflows/build-with-standard-clang.yml +++ b/.github/workflows/build-with-standard-clang.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] numpy_version: ["'numpy>=2'"] env: @@ -49,7 +49,7 @@ jobs: - name: Install mkl_random dependencies run: | - pip install meson-python ninja cmake cython mkl-devel + pip install meson-python ninja cmake "cython>=3.1.0" mkl-devel pip install ${{ matrix.numpy_version }} - name: Build mkl_random diff --git a/.github/workflows/build_pip.yml b/.github/workflows/build_pip.yml index 0a145d11..0ed45f2a 100644 --- a/.github/workflows/build_pip.yml +++ b/.github/workflows/build_pip.yml @@ -18,8 +18,21 @@ jobs: strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13"] use_pre: ["", "--pre"] + include: + - python: "3.14" + python_spec: "3.14.* *_cp314" + use_pre: "" + - python: "3.14" + python_spec: "3.14.* *_cp314" + use_pre: "--pre" + - python: "3.14" + python_spec: "3.14.* *_cp314t" + use_pre: "" + - python: "3.14" + python_spec: "3.14.* *_cp314t" + use_pre: "--pre" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -36,11 +49,11 @@ jobs: - name: Install MKL run: | - conda install mkl-devel + conda install mkl-devel "python=${{ matrix.python_spec || matrix.python }}" - name: Build with pip run: | - pip install --no-cache-dir meson-python ninja cmake cython + pip install --no-cache-dir meson-python ninja cmake "cython>=3.1.0" pip install --no-cache-dir numpy ${{ matrix.use_pre }} pip install -e . --no-build-isolation --no-deps --verbose pip install --no-cache-dir pytest diff --git a/.github/workflows/conda-package-cf.yml b/.github/workflows/conda-package-cf.yml index b76793d6..2dea67fb 100644 --- a/.github/workflows/conda-package-cf.yml +++ b/.github/workflows/conda-package-cf.yml @@ -31,6 +31,12 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" steps: - name: Cancel Previous Runs @@ -54,9 +60,9 @@ jobs: with: path: ~/.conda/pkgs key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('**/meta.yaml') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Add conda to system path @@ -68,7 +74,7 @@ jobs: - name: Build conda package run: | CHANNELS=(-c conda-forge --override-channels) - VERSIONS=(--python "${{ matrix.python }}" --numpy "${{ matrix.numpy }}") + VERSIONS=(--python "${{ matrix.python_spec || matrix.python }}" --numpy "${{ matrix.numpy }}") TEST=(--no-test) conda build \ @@ -80,7 +86,7 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} path: /usr/share/miniconda/conda-bld/linux-64/${{ env.PACKAGE_NAME }}-*.conda build_windows: @@ -101,6 +107,12 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" env: conda-bld: C:\Miniconda\conda-bld\win-64\ @@ -130,9 +142,9 @@ jobs: with: path: /home/runner/conda_pkgs_dir key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('**/meta.yaml') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Store conda paths as envs @@ -146,12 +158,12 @@ jobs: conda list -n base - name: Build conda package - run: conda build --no-test --python ${{ matrix.python }} --numpy ${{ matrix.numpy }} -c conda-forge --override-channels conda-recipe-cf + run: conda build --no-test --python "${{ matrix.python_spec || matrix.python }}" --numpy ${{ matrix.numpy }} -c conda-forge --override-channels conda-recipe-cf - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} path: ${{ env.CONDA_BLD }}${{ env.PACKAGE_NAME }}-*.conda test_linux: @@ -171,6 +183,12 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" env: CHANNELS: -c conda-forge --override-channels @@ -179,7 +197,7 @@ jobs: - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} - name: Add conda to system path run: echo "$CONDA/bin" >> "$GITHUB_PATH" @@ -202,7 +220,7 @@ jobs: . "$CONDA/etc/profile.d/conda.sh" PACKAGE_VERSION="$(python -c "${VER_SCRIPT1} ${VER_SCRIPT2}")" export PACKAGE_VERSION - conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" "python=${{ matrix.python }}" "numpy=${{ matrix.numpy }}" -c "$GITHUB_WORKSPACE/channel" -c conda-forge --override-channels --only-deps --dry-run > lockfile + conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" "python=${{ matrix.python_spec || matrix.python }}" "numpy=${{ matrix.numpy }}" -c "$GITHUB_WORKSPACE/channel" -c conda-forge --override-channels --only-deps --dry-run > lockfile cat lockfile - name: Set pkgs_dirs @@ -216,9 +234,9 @@ jobs: with: path: ~/.conda/pkgs key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('lockfile') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Install mkl_random @@ -226,7 +244,7 @@ jobs: . "$CONDA/etc/profile.d/conda.sh" PACKAGE_VERSION="$(python -c "${VER_SCRIPT1} ${VER_SCRIPT2}")" export PACKAGE_VERSION - conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" pytest "python=${{ matrix.python }}" "numpy=${{ matrix.numpy }}" -c "$GITHUB_WORKSPACE/channel" -c conda-forge --override-channels + conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" pytest "python=${{ matrix.python_spec || matrix.python }}" "numpy=${{ matrix.numpy }}" -c "$GITHUB_WORKSPACE/channel" -c conda-forge --override-channels # Test installed packages conda list @@ -255,6 +273,12 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" env: CHANNELS: -c conda-forge --override-channels @@ -263,14 +287,13 @@ jobs: - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} - uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4.0.1 with: miniforge-version: latest channels: conda-forge - activate-environment: ${{ env.TEST_ENV_NAME }} - python-version: ${{ matrix.python }} + activate-environment: base - name: Install conda-index run: | @@ -295,7 +318,7 @@ jobs: FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "%SCRIPT%"`) DO ( SET PACKAGE_VERSION=%%F ) - conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% python=${{ matrix.python }} numpy=${{ matrix.numpy }} -c ${{ env.GITHUB_WORKSPACE }}/channel ${{ env.CHANNELS }} --only-deps --dry-run > lockfile + conda create -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% "python=${{ matrix.python_spec || matrix.python }}" numpy=${{ matrix.numpy }} -c ${{ env.GITHUB_WORKSPACE }}/channel ${{ env.CHANNELS }} --only-deps --dry-run > lockfile - name: Cache conda packages uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 @@ -304,9 +327,9 @@ jobs: with: path: /home/runner/conda_pkgs_dir key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('lockfile') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Install mkl_random @@ -318,7 +341,7 @@ jobs: FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "%SCRIPT%"`) DO ( SET PACKAGE_VERSION=%%F ) - conda create -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% pytest python=${{ matrix.python }} numpy=${{ matrix.numpy }} -c ${{ env.GITHUB_WORKSPACE }}/channel ${{ env.CHANNELS }} + conda create -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% pytest "python=${{ matrix.python_spec || matrix.python }}" numpy=${{ matrix.numpy }} -c ${{ env.GITHUB_WORKSPACE }}/channel ${{ env.CHANNELS }} # Test installed packages conda list @@ -342,6 +365,12 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" steps: - name: Cancel Previous Runs @@ -368,9 +397,9 @@ jobs: with: path: /Users/runner/conda_pkgs_dir key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('**/meta.yaml') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Install conda-build @@ -388,7 +417,7 @@ jobs: shell: bash -el {0} run: | CHANNELS=(-c conda-forge --override-channels) - VERSIONS=(--python "${{ matrix.python }}" --numpy "${{ matrix.numpy }}") + VERSIONS=(--python "${{ matrix.python_spec || matrix.python }}" --numpy "${{ matrix.numpy }}") TEST=(--no-test) conda build \ @@ -400,7 +429,7 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} path: ${{ env.CONDA_BLD }}${{ env.PACKAGE_NAME }}-*.conda test_osx: @@ -419,12 +448,18 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" steps: - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} - uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4.0.1 with: @@ -452,7 +487,7 @@ jobs: run: | PACKAGE_VERSION="$(python -c "${VER_SCRIPT1} ${VER_SCRIPT2}")" export PACKAGE_VERSION - conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" "python=${{ matrix.python }}" "numpy=${{ matrix.numpy }}" -c "$GITHUB_WORKSPACE/channel" -c conda-forge --override-channels --only-deps --dry-run > lockfile + conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" "python=${{ matrix.python_spec || matrix.python }}" "numpy=${{ matrix.numpy }}" -c "$GITHUB_WORKSPACE/channel" -c conda-forge --override-channels --only-deps --dry-run > lockfile cat lockfile - name: Cache conda packages @@ -462,9 +497,9 @@ jobs: with: path: /Users/runner/conda_pkgs_dir key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('lockfile') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Install mkl_random @@ -472,7 +507,7 @@ jobs: run: | PACKAGE_VERSION="$(python -c "${VER_SCRIPT1} ${VER_SCRIPT2}")" export PACKAGE_VERSION - conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" pytest "python=${{ matrix.python }}" "numpy=${{ matrix.numpy }}" -c "$GITHUB_WORKSPACE/channel" -c conda-forge --override-channels + conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" pytest "python=${{ matrix.python_spec || matrix.python }}" "numpy=${{ matrix.numpy }}" -c "$GITHUB_WORKSPACE/channel" -c conda-forge --override-channels # Test installed packages conda list -n "${{ env.TEST_ENV_NAME }}" diff --git a/.github/workflows/conda-package.yml b/.github/workflows/conda-package.yml index c45bf389..fea31707 100644 --- a/.github/workflows/conda-package.yml +++ b/.github/workflows/conda-package.yml @@ -33,6 +33,12 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" steps: - name: Cancel Previous Runs @@ -56,9 +62,9 @@ jobs: with: path: ~/.conda/pkgs key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('**/meta.yaml') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Add conda to system path @@ -87,7 +93,7 @@ jobs: - name: Build conda package run: | CHANNELS=(-c https://software.repos.intel.com/python/conda -c conda-forge --override-channels) - VERSIONS=(--python "${{ matrix.python }}" --numpy "${{ matrix.numpy }}") + VERSIONS=(--python "${{ matrix.python_spec || matrix.python }}" --numpy "${{ matrix.numpy }}") TEST=(--no-test) conda build \ @@ -99,7 +105,7 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} path: ${{ env.CONDA_BLD }}${{ env.PACKAGE_NAME }}-*.conda build_windows: @@ -120,6 +126,12 @@ jobs: numpy: "2.3" - python: "3.14" numpy: "2.3" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + - python: "3.14" + numpy: "2.3" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" steps: - name: Cancel Previous Runs @@ -148,9 +160,9 @@ jobs: with: path: /home/runner/conda_pkgs_dir key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('**/meta.yaml') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Store conda paths as envs @@ -167,12 +179,12 @@ jobs: conda list -n base - name: Build conda package - run: conda build --no-test --python "${{ matrix.python }}" --numpy "${{ matrix.numpy }}" -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels conda-recipe + run: conda build --no-test --python "${{ matrix.python_spec || matrix.python }}" --numpy "${{ matrix.numpy }}" -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels conda-recipe - name: Upload artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} path: ${{ env.CONDA_BLD }}${{ env.PACKAGE_NAME }}-*.conda test_linux: @@ -181,9 +193,21 @@ jobs: strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13"] numpy: ['numpy">=2"'] experimental: [false] + include: + - python: "3.14" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + numpy: 'numpy">=2"' + experimental: false + - python: "3.14" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" + numpy: 'numpy">=2"' + experimental: false + continue-on-error: ${{ matrix.experimental }} env: CHANNELS: -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels @@ -192,7 +216,7 @@ jobs: - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} - name: Add conda to system path run: echo "$CONDA/bin" >> "$GITHUB_PATH" @@ -228,7 +252,7 @@ jobs: . "$CONDA/etc/profile.d/conda.sh" PACKAGE_VERSION="$(python -c "${VER_SCRIPT1} ${VER_SCRIPT2}")" export PACKAGE_VERSION - conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" "python=${{ matrix.python }}" ${{ matrix.numpy }} -c "$GITHUB_WORKSPACE/channel" -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels --only-deps --dry-run > lockfile + conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} -c "$GITHUB_WORKSPACE/channel" -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels --only-deps --dry-run > lockfile cat lockfile - name: Set pkgs_dirs @@ -242,9 +266,9 @@ jobs: with: path: ~/.conda/pkgs key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('lockfile') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- - name: Install mkl_random @@ -252,7 +276,7 @@ jobs: . "$CONDA/etc/profile.d/conda.sh" PACKAGE_VERSION="$(python -c "${VER_SCRIPT1} ${VER_SCRIPT2}")" export PACKAGE_VERSION - conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" pytest "python=${{ matrix.python }}" ${{ matrix.numpy }} -c "$GITHUB_WORKSPACE/channel" -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels + conda create -n "${{ env.TEST_ENV_NAME }}" "${PACKAGE_NAME}=${PACKAGE_VERSION}" pytest "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} -c "$GITHUB_WORKSPACE/channel" -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels conda activate "${{ env.TEST_ENV_NAME }}" # Test installed packages @@ -270,9 +294,21 @@ jobs: strategy: matrix: - python: ["3.10", "3.11", "3.12", "3.13", "3.14"] + python: ["3.10", "3.11", "3.12", "3.13"] numpy: ['numpy">=2"'] experimental: [false] + include: + - python: "3.14" + python_spec: "3.14.* *_cp314" + python_tag: "3.14" + numpy: 'numpy">=2"' + experimental: false + - python: "3.14" + python_spec: "3.14.* *_cp314t" + python_tag: "3.14t" + numpy: 'numpy">=2"' + experimental: false + continue-on-error: ${{ matrix.experimental }} env: CHANNELS: -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels @@ -281,15 +317,13 @@ jobs: - name: Download artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }} + name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_tag || matrix.python }} - uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4.0.1 with: - auto-update-conda: true miniforge-version: latest channels: conda-forge - activate-environment: ${{ env.TEST_ENV_NAME }} - python-version: ${{ matrix.python }} + activate-environment: base - name: Install conda-index run: | @@ -333,7 +367,7 @@ jobs: FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "%SCRIPT%"`) DO ( SET PACKAGE_VERSION=%%F ) - conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% python=${{ matrix.python }} ${{ matrix.numpy }} -c ${{ env.GITHUB_WORKSPACE }}/channel ${{ env.CHANNELS }} --only-deps --dry-run > lockfile + conda create -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} -c ${{ env.GITHUB_WORKSPACE }}/channel ${{ env.CHANNELS }} --only-deps --dry-run > lockfile - name: Cache conda packages uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 @@ -342,9 +376,9 @@ jobs: with: path: /home/runner/conda_pkgs_dir key: - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }} + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}-${{hashFiles('lockfile') }} restore-keys: | - ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}- + ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_tag || matrix.python }}- ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}- # add intel-openmp as an explicit dependency @@ -359,7 +393,7 @@ jobs: SET PACKAGE_VERSION=%%F ) SET "WORKAROUND_DEPENDENCIES=intel-openmp" - conda create -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %WORKAROUND_DEPENDENCIES% pytest python=${{ matrix.python }} ${{ matrix.numpy }} -c ${{ env.GITHUB_WORKSPACE }}/channel ${{ env.CHANNELS }} + conda create -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %WORKAROUND_DEPENDENCIES% pytest "python=${{ matrix.python_spec || matrix.python }}" ${{ matrix.numpy }} -c ${{ env.GITHUB_WORKSPACE }}/channel ${{ env.CHANNELS }} conda activate ${{ env.TEST_ENV_NAME }} # Test installed packages diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e54d3d..356619b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Added support for `array_like` (broadcastable) `low`/`high` bounds in `randint` [gh-168](https://github.com/IntelPython/mkl_random/pull/168) +* Added support for free-threaded (GIL-disabled) CPython builds: the Cython extension is compiled with `freethreading_compatible=True`, so importing `mkl_random` no longer re-enables the GIL [gh-159](https://github.com/IntelPython/mkl_random/pull/159) ### Changed * Pinned Cython in the Coverity Scan workflow so generated code stays stable between scans, and added `coverity/README.md` documenting the known Cython-boilerplate false positives and the scan review checklist [gh-164](https://github.com/IntelPython/mkl_random/pull/164) +* Raised the minimum build-time `Cython` requirement to `3.1.0`, the first release providing the `freethreading_compatible` directive [gh-159](https://github.com/IntelPython/mkl_random/pull/159) ### Fixed * Fixed `uniform` to return a Python `float` for scalar bounds with `size=None` instead of a 0-d array [gh-167](https://github.com/IntelPython/mkl_random/pull/167) +### Removed +* Removed the `python-gil` constraint from the conda recipes, which pinned `mkl_random` to GIL-enabled Python 3.14 builds [gh-159](https://github.com/IntelPython/mkl_random/pull/159) + ## [1.5.0] (08/12/2026) ### Added diff --git a/README.md b/README.md index 7e5f06a8..ce6d04ba 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ If you already have `mkl` and `numpy` installed (from your system or a conda env and want to reuse them instead of pulling fresh copies into an isolated build, first install the build dependencies: ```sh -pip install meson-python cmake ninja cython numpy mkl-devel +pip install meson-python cmake ninja "cython>=3.1.0" numpy mkl-devel ``` then build against the existing installation with: diff --git a/conda-recipe-cf/meta.yaml b/conda-recipe-cf/meta.yaml index c1dcd498..96b019f5 100644 --- a/conda-recipe-cf/meta.yaml +++ b/conda-recipe-cf/meta.yaml @@ -21,15 +21,13 @@ requirements: - cmake - ninja - python - - python-gil # [py>=314] - mkl-devel - - cython + - cython >=3.1.0 - numpy - pip - wheel >=0.41.3 run: - python - - python-gil # [py>=314] - {{ pin_compatible('numpy', min_pin="x.x", max_pin="x") }} - {{ pin_compatible('mkl', min_pin="x.x", max_pin="x") }} diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index dbdedbbe..03d03ca0 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -24,15 +24,13 @@ requirements: - cmake - ninja - python - - python-gil # [py>=314] - mkl-devel - - cython + - cython >=3.1.0 - numpy - pip - wheel >=0.41.3 run: - python - - python-gil # [py>=314] - {{ pin_compatible('mkl', min_pin="x.x", max_pin="x") }} - numpy >=1.26.4 diff --git a/mkl_random/mklrand.pyx b/mkl_random/mklrand.pyx index a3259170..c4491c1a 100644 --- a/mkl_random/mklrand.pyx +++ b/mkl_random/mklrand.pyx @@ -24,6 +24,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # cython: language_level=3 +# cython: freethreading_compatible=True cdef extern from "Python.h": void* PyMem_Malloc(size_t n) @@ -1631,13 +1632,13 @@ cdef class _MKLRandomState: cdef unsigned int stream_id cdef cnp.ndarray obj "arrayObject_obj" - if (brng): - brng_token, stream_id = _parse_brng_argument(brng) - else: - brng_token = irk_get_brng_and_stream_mkl( - self.internal_state, &stream_id - ) with self.lock: + if (brng): + brng_token, stream_id = _parse_brng_argument(brng) + else: + brng_token = irk_get_brng_and_stream_mkl( + self.internal_state, &stream_id + ) try: if seed is None: _errcode = irk_randomseed_mkl( @@ -6598,7 +6599,8 @@ cdef class _MKLRandomState: raise ValueError("n < 0") # numpy#20483: Avoids divide by 0 niter = sz // d if d else 0 - irk_multinomial_vec(self.internal_state, niter, mnix, n, d, pix) + with self.lock, nogil: + irk_multinomial_vec(self.internal_state, niter, mnix, n, d, pix) return multin @@ -6975,7 +6977,8 @@ cdef class MKLRandomState(_MKLRandomState): """ cdef int err, brng_id - err = irk_leapfrog_stream_mkl(self.internal_state, k, nstreams) + with self.lock: + err = irk_leapfrog_stream_mkl(self.internal_state, k, nstreams) if err == -1: raise ValueError("The stream state buffer is corrupted") @@ -6996,7 +6999,8 @@ cdef class MKLRandomState(_MKLRandomState): """ cdef int err, brng_id - err = irk_skipahead_stream_mkl(self.internal_state, nskips) + with self.lock: + err = irk_skipahead_stream_mkl(self.internal_state, nskips) if err == -1: raise ValueError("The stream state buffer is corrupted") @@ -7079,9 +7083,10 @@ cdef class MKLRandomState(_MKLRandomState): if (( lo) == lo) and ((hi) == hi): if size is None: - irk_discrete_uniform_vec( - self.internal_state, 1, &rv_int, lo, hi - ) + with self.lock, nogil: + irk_discrete_uniform_vec( + self.internal_state, 1, &rv_int, lo, hi + ) return rv_int else: array = np.empty(size, np.int32) @@ -7098,9 +7103,10 @@ cdef class MKLRandomState(_MKLRandomState): return array else: if size is None: - irk_discrete_uniform_long_vec( - self.internal_state, 1, &rv_long, lo, hi - ) + with self.lock, nogil: + irk_discrete_uniform_long_vec( + self.internal_state, 1, &rv_long, lo, hi + ) return rv_long else: array = np.empty(size, int) @@ -7283,35 +7289,38 @@ cdef class MKLRandomState(_MKLRandomState): method, [ICDF, BOXMULLER2, BOXMULLER], _method_alias_dict_gaussian ) if (method is ICDF): - irk_multinormal_vec_ICDF( - self.internal_state, - n, - res_data, - dim, - mean_data, - t_data, - storage_mode - ) + with self.lock, nogil: + irk_multinormal_vec_ICDF( + self.internal_state, + n, + res_data, + dim, + mean_data, + t_data, + storage_mode + ) elif (method is BOXMULLER2): - irk_multinormal_vec_BM2( - self.internal_state, - n, - res_data, - dim, - mean_data, - t_data, - storage_mode - ) + with self.lock, nogil: + irk_multinormal_vec_BM2( + self.internal_state, + n, + res_data, + dim, + mean_data, + t_data, + storage_mode + ) else: - irk_multinormal_vec_BM1( - self.internal_state, - n, - res_data, - dim, - mean_data, - t_data, - storage_mode - ) + with self.lock, nogil: + irk_multinormal_vec_BM1( + self.internal_state, + n, + res_data, + dim, + mean_data, + t_data, + storage_mode + ) return resarr diff --git a/mkl_random/tests/test_freethreading.py b/mkl_random/tests/test_freethreading.py new file mode 100644 index 00000000..a2aaa3d1 --- /dev/null +++ b/mkl_random/tests/test_freethreading.py @@ -0,0 +1,142 @@ +# Copyright (c) 2026, Intel Corporation +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Intel Corporation nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os +import sys +import sysconfig +import threading + +import numpy as np +import pytest + +# Oversubscription: MKL spawns its own thread pool per calling thread, so +# generating from many Python threads concurrently can spawn far more OS +# threads than cores. Cap it before mkl_random/MKL initialize. +os.environ.setdefault("MKL_NUM_THREADS", "1") + +import mkl_random # noqa: E402 + +FREE_THREADED = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) + + +def _run_on_threads(worker, n_threads): + # Run worker(i) on n_threads and fail if any thread raised. + errors = [] + + def wrapped(i): + try: + worker(i) + except Exception as exc: # pylint: disable=broad-except + errors.append(exc) + + threads = [ + threading.Thread(target=wrapped, args=(i,)) for i in range(n_threads) + ] + for t in threads: + t.start() + for t in threads: + t.join() + + assert not errors + + +def test_concurrent_sampling_per_instance(): + # Each thread owns a private MKLRandomState seeded identically, so the + # per-instance lock + `nogil` sampling must reproduce the single-threaded + # result exactly regardless of concurrency. + n_threads = 4 + size = 10**5 + 1 # large enough that per-thread nogil sampling overlaps + seed = 1234 + expected = mkl_random.MKLRandomState(seed).normal(size=size) + results = [None] * n_threads + + def worker(i): + results[i] = mkl_random.MKLRandomState(seed).normal(size=size) + + _run_on_threads(worker, n_threads) + + for r in results: + np.testing.assert_array_equal(r, expected) + + +def test_concurrent_shared_singleton(): + # Module-level functions share a single lock-guarded RandomState. Hammering + # it from many threads must not corrupt state, crash, or return garbage. + n_threads = 8 + size = 10**5 + 1 + results = [None] * n_threads + + def worker(i): + results[i] = mkl_random.uniform(size=size) + + _run_on_threads(worker, n_threads) + + for r in results: + assert r.shape == (size,) + assert np.all(np.isfinite(r)) + + +def test_concurrent_patch_restore(): + n_threads = 8 + n_iters = 20 + + def worker(_i): + for _ in range(n_iters): + mkl_random.patch_numpy_random() + mkl_random.restore_numpy_random() + + _run_on_threads(worker, n_threads) + + assert not mkl_random.is_patched() + + +def test_concurrent_multinormal_cholesky_shared(): + # A data race on the shared stream reuses values; assert few duplicates. + n_threads = 8 + mean = np.zeros(1) + ch = np.eye(1) + rng = mkl_random.MKLRandomState(12345) + chunks = [None] * n_threads + + def worker(i): + parts = [ + rng.multinormal_cholesky(mean, ch, size=4000).ravel() + for _ in range(25) + ] + chunks[i] = np.concatenate(parts) + + _run_on_threads(worker, n_threads) + + allvals = np.concatenate(chunks) + assert np.all(np.isfinite(allvals)) + dup_frac = 1.0 - np.unique(allvals).size / allvals.size + assert dup_frac < 0.01, f"shared stream corrupted: {dup_frac:.3%} dups" + + +@pytest.mark.skipif( + not FREE_THREADED, reason="requires a free-threaded CPython build" +) +def test_gil_not_reenabled_on_import(): + assert not sys._is_gil_enabled() # pylint: disable=no-member diff --git a/pyproject.toml b/pyproject.toml index c841c9a3..983c3601 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ build-backend = "mesonpy" requires = [ "meson-python>=0.16.0", "ninja", - "Cython", + "Cython>=3.1.0", "numpy", "mkl-devel", "cmake" @@ -50,6 +50,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: Free Threading :: 2 - Beta", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development", "Topic :: Scientific/Engineering",