diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c3bf56e0..e116c483 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,20 +32,23 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install black 'pylint<3' 'isort[colors]<6' - pip install -v --editable . + pip install -v --editable .[lint] - name: black check run: | python -m black --check --diff --color . + - name: isort check + run: | + python -m isort --check --diff --color . + - name: pylint check run: | python -m pylint src/gstools/ - - name: isort check + - name: cython-lint check run: | - python -m isort --check --diff --color . + cython-lint src/gstools/ build_wheels: name: wheels for ${{ matrix.cfg.os }} / ${{ matrix.cfg.arch }} @@ -68,7 +71,7 @@ jobs: fetch-depth: '0' - name: Build wheels - uses: pypa/cibuildwheel@v2.11.2 + uses: pypa/cibuildwheel@v2.16.2 env: CIBW_ARCHS: ${{ matrix.cfg.arch }} with: @@ -85,7 +88,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 diff --git a/.readthedocs.yml b/.readthedocs.yml index 2ef05a17..7dd17607 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,12 +1,16 @@ version: 2 +build: + os: ubuntu-22.04 + tools: + python: "3.11" + sphinx: configuration: docs/source/conf.py formats: all python: - version: 3.8 install: - method: pip path: . diff --git a/CHANGELOG.md b/CHANGELOG.md index 633b50b4..00513d23 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to **GSTools** will be documented in this file. +## [1.5.1] - Nifty Neon - 2023-11 + +### Enhancements + +see [#317](https://github.com/GeoStat-Framework/GSTools/pull/317) + +- added wheels for Python 3.12 +- dropped support for Python 3.7 (EOL) +- linted Cython files with cython-lint +- use Cython 3 to build extensions + + ## [1.5.0] - Nifty Neon - 2023-06 ### Enhancements @@ -404,7 +416,8 @@ See: [#197](https://github.com/GeoStat-Framework/GSTools/issues/197) First release of GSTools. -[Unreleased]: https://github.com/GeoStat-Framework/gstools/compare/v1.5.0...HEAD +[Unreleased]: https://github.com/GeoStat-Framework/gstools/compare/v1.5.1...HEAD +[1.5.1]: https://github.com/GeoStat-Framework/gstools/compare/v1.5.0...v1.5.1 [1.5.0]: https://github.com/GeoStat-Framework/gstools/compare/v1.4.1...v1.5.0 [1.4.1]: https://github.com/GeoStat-Framework/gstools/compare/v1.4.0...v1.4.1 [1.4.0]: https://github.com/GeoStat-Framework/gstools/compare/v1.3.5...v1.4.0 diff --git a/pyproject.toml b/pyproject.toml index 3a6c7ec0..70b7d0a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,13 +3,13 @@ requires = [ "setuptools>=64", "setuptools_scm>=7", "oldest-supported-numpy", - "Cython>=0.29.32,<3.0", + "Cython>=3.0", "extension-helpers>=1", ] build-backend = "setuptools.build_meta" [project] -requires-python = ">=3.7" +requires-python = ">=3.8" name = "gstools" description = "GSTools: A geostatistical toolbox." authors = [ @@ -32,11 +32,11 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: GIS", "Topic :: Scientific/Engineering :: Hydrology", @@ -72,6 +72,12 @@ plotting = [ ] rust = ["gstools_core>=0.2.0,<1"] test = ["pytest-cov>=3"] +lint = [ + "black", + "pylint", + "isort[colors]", + "cython-lint", +] [project.urls] Changelog = "https://github.com/GeoStat-Framework/GSTools/blob/main/CHANGELOG.md" @@ -98,11 +104,11 @@ line_length = 79 [tool.black] line-length = 79 target-version = [ - "py37", "py38", "py39", "py310", "py311", + "py312", ] [tool.coverage] @@ -154,8 +160,8 @@ target-version = [ [tool.cibuildwheel] # Switch to using build build-frontend = "build" -# Disable building PyPy wheels on all platforms, 32bit for py3.10/11 and musllinux builds, py3.6 -skip = ["cp36-*", "pp*", "cp31*-win32", "cp31*-manylinux_i686", "*-musllinux_*"] +# Disable building PyPy wheels on all platforms, 32bit for py3.10/11/12, musllinux builds, py3.6/7 +skip = ["cp36-*", "cp37-*", "pp*", "cp31*-win32", "cp31*-manylinux_i686", "*-musllinux_*"] # Run the package tests using `pytest` test-extras = "test" test-command = "pytest -v {package}/tests" diff --git a/src/gstools/field/summator.pyx b/src/gstools/field/summator.pyx index cad20e1d..29239c07 100644 --- a/src/gstools/field/summator.pyx +++ b/src/gstools/field/summator.pyx @@ -1,12 +1,9 @@ -#cython: language_level=3, boundscheck=False, wraparound=False, cdivision=True +# cython: language_level=3, boundscheck=False, wraparound=False, cdivision=True """ This is the randomization method summator, implemented in cython. """ import numpy as np - -cimport cython - from cython.parallel import prange cimport numpy as np @@ -18,7 +15,7 @@ def summate( const double[:] z_1, const double[:] z_2, const double[:, :] pos - ): +): cdef int i, j, d cdef double phase cdef int dim = pos.shape[0] @@ -53,7 +50,7 @@ def summate_incompr( const double[:] z_1, const double[:] z_2, const double[:, :] pos - ): +): cdef int i, j, d cdef double phase cdef double k_2 @@ -76,6 +73,7 @@ def summate_incompr( phase += cov_samples[d, j] * pos[d, i] for d in range(dim): proj[d] = e1[d] - cov_samples[d, j] * cov_samples[0, j] / k_2 - summed_modes[d, i] += proj[d] * (z_1[j] * cos(phase) + z_2[j] * sin(phase)) - + summed_modes[d, i] += ( + proj[d] * (z_1[j] * cos(phase) + z_2[j] * sin(phase)) + ) return np.asarray(summed_modes) diff --git a/src/gstools/krige/krigesum.pyx b/src/gstools/krige/krigesum.pyx index de3a43b1..2f79d3ad 100644 --- a/src/gstools/krige/krigesum.pyx +++ b/src/gstools/krige/krigesum.pyx @@ -1,12 +1,11 @@ -#cython: language_level=3, boundscheck=False, wraparound=False, cdivision=True +# cython: language_level=3, boundscheck=False, wraparound=False, cdivision=True """ This is a summator for the kriging routines """ import numpy as np - -cimport cython from cython.parallel import prange + cimport numpy as np diff --git a/src/gstools/variogram/estimator.pyx b/src/gstools/variogram/estimator.pyx index 528004fe..611f5efb 100644 --- a/src/gstools/variogram/estimator.pyx +++ b/src/gstools/variogram/estimator.pyx @@ -1,13 +1,10 @@ -#cython: language_level=3, boundscheck=False, wraparound=False, cdivision=True +# cython: language_level=3, boundscheck=False, wraparound=False, cdivision=True # distutils: language = c++ """ This is the variogram estimater, implemented in cython. """ import numpy as np - -cimport cython - from cython.parallel import parallel, prange cimport numpy as np @@ -16,20 +13,20 @@ from libc.math cimport M_PI, acos, atan2, cos, fabs, isnan, pow, sin, sqrt cdef inline double dist_euclid( const int dim, - const double[:,:] pos, + const double[:, :] pos, const int i, const int j, ) nogil: cdef int d cdef double dist_squared = 0.0 for d in range(dim): - dist_squared += ((pos[d,i] - pos[d,j]) * (pos[d,i] - pos[d,j])) + dist_squared += ((pos[d, i] - pos[d, j]) * (pos[d, i] - pos[d, j])) return sqrt(dist_squared) cdef inline double dist_haversine( const int dim, - const double[:,:] pos, + const double[:, :] pos, const int i, const int j, ) nogil: @@ -48,7 +45,7 @@ cdef inline double dist_haversine( ctypedef double (*_dist_func)( const int, - const double[:,:], + const double[:, :], const int, const int, ) nogil @@ -56,9 +53,9 @@ ctypedef double (*_dist_func)( cdef inline bint dir_test( const int dim, - const double[:,:] pos, + const double[:, :] pos, const double dist, - const double[:,:] direction, + const double[:, :] direction, const double angles_tol, const double bandwidth, const int i, @@ -74,12 +71,12 @@ cdef inline bint dir_test( # scalar-product calculation for bandwidth projection and angle calculation for k in range(dim): - s_prod += (pos[k,i] - pos[k,j]) * direction[d,k] + s_prod += (pos[k, i] - pos[k, j]) * direction[d, k] # calculate band-distance by projection of point-pair-vec to direction line if bandwidth > 0.0: for k in range(dim): - tmp = (pos[k,i] - pos[k,j]) - s_prod * direction[d,k] + tmp = (pos[k, i] - pos[k, j]) - s_prod * direction[d, k] b_dist += tmp * tmp in_band = sqrt(b_dist) < bandwidth @@ -130,32 +127,31 @@ ctypedef void (*_normalization_func)( ) cdef inline void normalization_matheron_vec( - double[:,:] variogram, - long[:,:] counts, + double[:, :] variogram, + long[:, :] counts, ): - cdef int d, i + cdef int d for d in range(variogram.shape[0]): normalization_matheron(variogram[d, :], counts[d, :]) cdef inline void normalization_cressie_vec( - double[:,:] variogram, - long[:,:] counts, + double[:, :] variogram, + long[:, :] counts, ): - cdef int d, i - cdef long cnt + cdef int d for d in range(variogram.shape[0]): normalization_cressie(variogram[d, :], counts[d, :]) ctypedef void (*_normalization_func_vec)( - double[:,:], - long[:,:], + double[:, :], + long[:, :], ) cdef _estimator_func choose_estimator_func(str estimator_type): cdef _estimator_func estimator_func if estimator_type == 'm': estimator_func = estimator_matheron - else: # estimator_type == 'c' + else: # estimator_type == 'c' estimator_func = estimator_cressie return estimator_func @@ -163,7 +159,7 @@ cdef _normalization_func choose_estimator_normalization(str estimator_type): cdef _normalization_func normalization_func if estimator_type == 'm': normalization_func = normalization_matheron - else: # estimator_type == 'c' + else: # estimator_type == 'c' normalization_func = normalization_cressie return normalization_func @@ -171,16 +167,16 @@ cdef _normalization_func_vec choose_estimator_normalization_vec(str estimator_ty cdef _normalization_func_vec normalization_func_vec if estimator_type == 'm': normalization_func_vec = normalization_matheron_vec - else: # estimator_type == 'c' + else: # estimator_type == 'c' normalization_func_vec = normalization_cressie_vec return normalization_func_vec def directional( - const double[:,:] f, + const double[:, :] f, const double[:] bin_edges, - const double[:,:] pos, - const double[:,:] direction, # should be normed + const double[:, :] pos, + const double[:, :] direction, # should be normed const double angles_tol=M_PI/8.0, const double bandwidth=-1.0, # negative values to turn of bandwidth search const bint separate_dirs=False, # whether the direction bands don't overlap @@ -207,8 +203,8 @@ def directional( cdef int k_max = pos.shape[1] cdef int f_max = f.shape[0] - cdef double[:,:] variogram = np.zeros((d_max, len(bin_edges)-1)) - cdef long[:,:] counts = np.zeros((d_max, len(bin_edges)-1), dtype=long) + cdef double[:, :] variogram = np.zeros((d_max, len(bin_edges)-1)) + cdef long[:, :] counts = np.zeros((d_max, len(bin_edges)-1), dtype=long) cdef int i, j, k, m, d cdef double dist @@ -219,13 +215,15 @@ def directional( if dist < bin_edges[i] or dist >= bin_edges[i+1]: continue # skip if not in current bin for d in range(d_max): - if not dir_test(dim, pos, dist, direction, angles_tol, bandwidth, k, j, d): + if not dir_test( + dim, pos, dist, direction, angles_tol, bandwidth, k, j, d + ): continue # skip if not in current direction for m in range(f_max): # skip no data values - if not (isnan(f[m,k]) or isnan(f[m,j])): + if not (isnan(f[m, k]) or isnan(f[m, j])): counts[d, i] += 1 - variogram[d, i] += estimator_func(f[m,k] - f[m,j]) + variogram[d, i] += estimator_func(f[m, k] - f[m, j]) # once we found a fitting direction # break the search if directions are separated if separate_dirs: @@ -234,10 +232,11 @@ def directional( normalization_func_vec(variogram, counts) return np.asarray(variogram), np.asarray(counts) + def unstructured( - const double[:,:] f, + const double[:, :] f, const double[:] bin_edges, - const double[:,:] pos, + const double[:, :] pos, str estimator_type='m', str distance_type='e', ): @@ -280,15 +279,15 @@ def unstructured( continue # skip if not in current bin for m in range(f_max): # skip no data values - if not (isnan(f[m,k]) or isnan(f[m,j])): + if not (isnan(f[m, k]) or isnan(f[m, j])): counts[i] += 1 - variogram[i] += estimator_func(f[m,k] - f[m,j]) + variogram[i] += estimator_func(f[m, k] - f[m, j]) normalization_func(variogram, counts) return np.asarray(variogram), np.asarray(counts) -def structured(const double[:,:] f, str estimator_type='m'): +def structured(const double[:, :] f, str estimator_type='m'): cdef _estimator_func estimator_func = choose_estimator_func(estimator_type) cdef _normalization_func normalization_func = ( choose_estimator_normalization(estimator_type) @@ -307,15 +306,15 @@ def structured(const double[:,:] f, str estimator_type='m'): for j in range(j_max): for k in prange(1, k_max-i): counts[k] += 1 - variogram[k] += estimator_func(f[i,j] - f[i+k,j]) + variogram[k] += estimator_func(f[i, j] - f[i+k, j]) normalization_func(variogram, counts) return np.asarray(variogram) def ma_structured( - const double[:,:] f, - const bint[:,:] mask, + const double[:, :] f, + const bint[:, :] mask, str estimator_type='m', ): cdef _estimator_func estimator_func = choose_estimator_func(estimator_type) @@ -335,9 +334,9 @@ def ma_structured( for i in range(i_max): for j in range(j_max): for k in prange(1, k_max-i): - if not mask[i,j] and not mask[i+k,j]: + if not mask[i, j] and not mask[i+k, j]: counts[k] += 1 - variogram[k] += estimator_func(f[i,j] - f[i+k,j]) + variogram[k] += estimator_func(f[i, j] - f[i+k, j]) normalization_func(variogram, counts) return np.asarray(variogram)