Skip to content

Commit 445af61

Browse files
authored
Merge pull request #11 from GeoStat-Framework/add_py314
CI: add py314, remove py39, update cibw, cleanup
2 parents b69879c + 769a19d commit 445af61

File tree

8 files changed

+140
-30
lines changed

8 files changed

+140
-30
lines changed

.github/workflows/main.yml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,16 @@ jobs:
5858
strategy:
5959
fail-fast: false
6060
matrix:
61-
# macos-13 is an intel runner, macos-latest is apple silicon
62-
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-latest]
61+
# macos-15-intel is an intel runner, macos-latest is apple silicon
62+
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm, macos-15-intel, macos-latest]
6363

6464
steps:
6565
- uses: actions/checkout@v4
6666
with:
6767
fetch-depth: "0"
6868

6969
- name: Build wheels
70-
uses: pypa/cibuildwheel@v2.22.0
70+
uses: pypa/cibuildwheel@v3.3.0
7171
with:
7272
output-dir: dist-wheel-${{ matrix.os }}
7373

@@ -82,19 +82,17 @@ jobs:
8282
strategy:
8383
fail-fast: false
8484
matrix:
85-
os: [ubuntu-latest, windows-latest, macos-13, macos-14]
85+
os: [ubuntu-latest, windows-latest, macos-15-intel, macos-latest]
8686
# https://github.com/scipy/oldest-supported-numpy/blob/main/setup.cfg
8787
ver:
88-
- { py: "3.9", np: "==1.20.0" }
8988
- { py: "3.10", np: "==1.21.6" }
9089
- { py: "3.11", np: "==1.23.2" }
9190
- { py: "3.12", np: "==1.26.2" }
9291
- { py: "3.13", np: "==2.1.0" }
93-
- { py: "3.13", np: ">=2.1.0" }
92+
- { py: "3.14", np: "==2.3.2" }
93+
- { py: "3.14", np: ">=2.3.2" }
9494
exclude:
95-
- os: macos-14
96-
ver: { py: "3.9", np: "==1.20.0" }
97-
- os: macos-14
95+
- os: macos-latest
9896
ver: { py: "3.10", np: "==1.21.6" }
9997
steps:
10098
- uses: actions/checkout@v4
@@ -155,14 +153,14 @@ jobs:
155153
- name: Install GSTools-Cython
156154
env:
157155
GSTOOLS_CY_COV: 1
156+
GSTOOLS_BUILD_PARALLEL: 1
158157
run: |
159158
pip install -v --editable .[test]
160159
161160
- name: Run tests
162161
env:
163162
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
164163
run: |
165-
pip install "numpy${{ matrix.ver.np }}"
166164
python -m pytest --cov gstools_cython --cov-report term-missing -v tests/
167165
python -m coveralls --service=github
168166

CHANGELOG.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
All notable changes to **GSTools-Cython** will be documented in this file.
44

5+
## [1.2.0] - 2025-12
6+
7+
See [#11](https://github.com/GeoStat-Framework/GSTools-Cython/pull/11)
8+
9+
### Changes
10+
11+
- add support for Python 3.14 (incl. free-threaded support)
12+
- move pypy version to 3.11
13+
- add win arm64 wheels (without Python 3.10, since there are no numpy wheels prior to 3.11)
14+
- remove support for Python 3.9 (EOL)
15+
- fix bug in error message in variogram.pyx (undetected by cython<3.1)
16+
- update pyproject.toml and use setuptools>=77
17+
- increased coverage
18+
519
## [1.1.0] - 2025-04
620

721
See [#5](https://github.com/GeoStat-Framework/GSTools-Cython/pull/5)
@@ -23,6 +37,7 @@ First release of GSTools-Cython
2337
- moved Cython files into this separate package
2438

2539

26-
[Unreleased]: https://github.com/GeoStat-Framework/gstools-cython/compare/v1.1.0...HEAD
40+
[Unreleased]: https://github.com/GeoStat-Framework/gstools-cython/compare/v1.2.0...HEAD
41+
[1.2.0]: https://github.com/GeoStat-Framework/gstools-cython/compare/v1.1.0...v1.2.0
2742
[1.1.0]: https://github.com/GeoStat-Framework/gstools-cython/compare/v1.0.0...v1.1.0
2843
[1.0.0]: https://github.com/GeoStat-Framework/gstools-cython/releases/tag/v1.0.0

docs/source/conf.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,13 @@ def setup(app):
203203
# latex_show_urls = 'footnote'
204204
# http://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-latex-output
205205
latex_elements = {
206-
"preamble": r"""
206+
"preamble": (
207+
r"""
207208
\setcounter{secnumdepth}{1}
208209
\setcounter{tocdepth}{2}
209210
\pagestyle{fancy}
210-
""",
211+
"""
212+
),
211213
"pointsize": "10pt",
212214
"papersize": "a4paper",
213215
"fncychap": "\\usepackage[Glenn]{fncychap}",

pyproject.toml

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,42 @@
11
[build-system]
22
requires = [
3-
"setuptools>=64",
4-
"setuptools<72.2; implementation_name == 'pypy'", # https://github.com/pypa/distutils/issues/283
3+
"setuptools>=77",
54
"setuptools_scm>=7",
6-
"numpy>=2.0.0rc1",
7-
"Cython>=3.0.10,<3.1.0",
5+
"numpy>=2",
6+
"Cython>=3",
87
"extension-helpers>=1",
98
]
109
build-backend = "setuptools.build_meta"
1110

1211
[project]
13-
requires-python = ">=3.9"
12+
requires-python = ">=3.10"
1413
name = "gstools_cython"
1514
description = "Cython backend for GSTools."
1615
authors = [
1716
{name = "Sebastian Müller, Lennart Schüler", email = "info@geostat-framework.org"},
1817
]
1918
readme = "README.md"
20-
license = {text = "LGPL-3.0"}
19+
license = "LGPL-3.0-or-later"
20+
license-files = ["LICENSE"]
2121
dynamic = ["version"]
2222
classifiers = [
2323
"Development Status :: 5 - Production/Stable",
2424
"Intended Audience :: Developers",
2525
"Intended Audience :: End Users/Desktop",
2626
"Intended Audience :: Science/Research",
2727
"Intended Audience :: Education",
28-
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
2928
"Natural Language :: English",
3029
"Operating System :: Unix",
3130
"Operating System :: Microsoft",
3231
"Operating System :: MacOS",
3332
"Programming Language :: Python",
3433
"Programming Language :: Python :: 3",
3534
"Programming Language :: Python :: 3 :: Only",
36-
"Programming Language :: Python :: 3.9",
3735
"Programming Language :: Python :: 3.10",
3836
"Programming Language :: Python :: 3.11",
3937
"Programming Language :: Python :: 3.12",
4038
"Programming Language :: Python :: 3.13",
39+
"Programming Language :: Python :: 3.14",
4140
"Topic :: Scientific/Engineering",
4241
"Topic :: Scientific/Engineering :: GIS",
4342
"Topic :: Scientific/Engineering :: Hydrology",
@@ -58,7 +57,7 @@ doc = [
5857
]
5958
test = [
6059
"pytest-cov>=3",
61-
"Cython>=3.0.10,<3.1.0",
60+
"Cython>=3",
6261
]
6362
lint = [
6463
"black>=24",
@@ -75,9 +74,6 @@ Homepage = "https://geostat-framework.org/#gstools"
7574
Source = "https://github.com/GeoStat-Framework/GSTools-Cython"
7675
Tracker = "https://github.com/GeoStat-Framework/GSTools-Cython/issues"
7776

78-
[tool.setuptools]
79-
license-files = ["LICENSE"]
80-
8177
[tool.setuptools_scm]
8278
write_to = "src/gstools_cython/_version.py"
8379
write_to_template = "__version__ = '{version}'"
@@ -90,11 +86,11 @@ multi_line_output = 3
9086

9187
[tool.black]
9288
target-version = [
93-
"py39",
9489
"py310",
9590
"py311",
9691
"py312",
9792
"py313",
93+
"py314",
9894
]
9995

10096
[tool.coverage]
@@ -145,8 +141,8 @@ target-version = [
145141
build-frontend = "build"
146142
# explicitly enable pypy
147143
enable = ["pypy"]
148-
# Disable building py3.6/7/8, pp3.8, 32bit linux
149-
skip = ["cp36-*", "cp37-*", "cp38-*", "pp38-*", "*_i686"]
144+
# Disable building py3.8/9, 32bit linux, and arm64 windows wheels for python 3.10 (no numpy support)
145+
skip = ["cp38-*", "cp39-*", "*_i686", "cp310-win_arm64"]
150146
# Run the package tests using `pytest`
151147
test-extras = "test"
152148
test-command = "pytest -v {package}/tests"

src/gstools_cython/variogram.pyx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def directional(
253253
counts of samples per bin and direciton
254254
"""
255255
if pos.shape[1] != f.shape[1]:
256-
raise ValueError(f'len(pos) = {pos.shape[1]} != len(f) = {f.shape[1])}')
256+
raise ValueError(f'len(pos) = {pos.shape[1]} != len(f) = {f.shape[1]}')
257257

258258
if bin_edges.shape[0] < 2:
259259
raise ValueError('len(bin_edges) too small')
@@ -359,7 +359,7 @@ def unstructured(
359359
raise ValueError(f'Haversine: dim = {dim} != 2')
360360

361361
if pos.shape[1] != f.shape[1]:
362-
raise ValueError(f'len(pos) = {pos.shape[1]} != len(f) = {f.shape[1])}')
362+
raise ValueError(f'len(pos) = {pos.shape[1]} != len(f) = {f.shape[1]}')
363363

364364
if bin_edges.shape[0] < 2:
365365
raise ValueError('len(bin_edges) too small')

tests/test_field.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ def test_summate(self):
6969
)
7070
summed = gs_cy.field.summate(cov_samples, z_1, z_2, pos)
7171
np.testing.assert_allclose(summed_modes, summed)
72+
summed_threads = gs_cy.field.summate(cov_samples, z_1, z_2, pos, num_threads=2)
73+
np.testing.assert_allclose(summed_modes, summed_threads)
7274

7375
def test_summate_incompr(self):
7476
# x = y = np.linspace(0,1,3)

tests/test_krige.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ def test_calc_field_krige_and_variance(self):
4444
)
4545
np.testing.assert_allclose(field, self.field_ref)
4646
np.testing.assert_allclose(error, self.error_ref)
47+
field_threads, error_threads = gs_cy.krige.calc_field_krige_and_variance(
48+
self.krig_mat, self.krig_vecs, self.cond, num_threads=2
49+
)
50+
np.testing.assert_allclose(field_threads, self.field_ref)
51+
np.testing.assert_allclose(error_threads, self.error_ref)
4752

4853
def test_calc_field_krige(self):
4954
field = gs_cy.krige.calc_field_krige(self.krig_mat, self.krig_vecs, self.cond)

tests/test_variogram.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,58 @@ def test_directional(self):
3434
self.assertAlmostEqual(gamma[1, len(gamma[0]) // 2], var, places=2)
3535
self.assertAlmostEqual(gamma[1, -1], var, places=2)
3636

37+
def test_directional_separate_dirs(self):
38+
pos = np.array(((0.0, 1.0), (0.0, 0.0)), dtype=np.double)
39+
dirs = np.array(((1.0, 0.0), (1.0, 0.0)), dtype=np.double)
40+
field = np.array(((1.0, 2.0),), dtype=np.double)
41+
bins = np.array((0.0, 2.0), dtype=np.double)
42+
43+
_, counts = gs_cy.variogram.directional(
44+
field, bins, pos, dirs, angles_tol=np.pi, separate_dirs=True
45+
)
46+
47+
self.assertEqual(counts[0, 0], 1)
48+
self.assertEqual(counts[1, 0], 0)
49+
50+
def test_directional_bandwidth_cressie(self):
51+
pos = np.array(((0.0, 0.0, 1.0), (0.0, 1.0, 0.0)), dtype=np.double)
52+
dirs = np.array(((1.0, 0.0),), dtype=np.double)
53+
field = np.array(((1.0, 2.0, 5.0),), dtype=np.double)
54+
bins = np.array((0.0, 2.0), dtype=np.double)
55+
56+
gamma, counts = gs_cy.variogram.directional(
57+
field,
58+
bins,
59+
pos,
60+
dirs,
61+
angles_tol=np.pi,
62+
bandwidth=0.5,
63+
estimator_type="c",
64+
)
65+
66+
self.assertEqual(counts[0, 0], 1)
67+
f_diff = field[0, 2] - field[0, 0]
68+
raw = np.sqrt(abs(f_diff))
69+
expected = 0.5 * raw**4 / (0.457 + 0.494 + 0.045)
70+
self.assertAlmostEqual(gamma[0, 0], expected, places=6)
71+
72+
def test_directional_error_checks(self):
73+
pos = np.array(((0.0, 1.0), (0.0, 1.0)), dtype=np.double)
74+
dirs = np.array(((1.0, 0.0),), dtype=np.double)
75+
bins = np.array((0.0, 1.0), dtype=np.double)
76+
field = np.array(((1.0, 2.0),), dtype=np.double)
77+
78+
with self.assertRaises(ValueError):
79+
gs_cy.variogram.directional(field[:, :1], bins, pos, dirs)
80+
81+
with self.assertRaises(ValueError):
82+
gs_cy.variogram.directional(
83+
field, np.array((0.0,), dtype=np.double), pos, dirs
84+
)
85+
86+
with self.assertRaises(ValueError):
87+
gs_cy.variogram.directional(field, bins, pos, dirs, angles_tol=0.0)
88+
3789
def test_unstructured(self):
3890
x = np.arange(1, 11, 1, dtype=np.double)
3991
z = np.array(
@@ -73,6 +125,46 @@ def test_unstructured(self):
73125
self.assertAlmostEqual(gamma[len(gamma) // 2], var, places=2)
74126
self.assertAlmostEqual(gamma[-1], var, places=2)
75127

128+
def test_unstructured_haversine(self):
129+
pos = np.array(((0.0, 0.0), (0.0, 90.0)), dtype=np.double)
130+
field = np.array(((1.0, 3.0),), dtype=np.double)
131+
bins = np.array((0.0, 2.0), dtype=np.double)
132+
133+
gamma, counts = gs_cy.variogram.unstructured(
134+
field, bins, pos, distance_type="h"
135+
)
136+
137+
self.assertEqual(counts[0], 1)
138+
self.assertAlmostEqual(gamma[0], 2.0, places=6)
139+
140+
def test_unstructured_num_threads(self):
141+
pos = np.array(((0.0, 1.0, 2.0),), dtype=np.double)
142+
field = np.array(((1.0, 3.0, 2.0),), dtype=np.double)
143+
bins = np.array((0.0, 2.0), dtype=np.double)
144+
145+
gamma_default, counts_default = gs_cy.variogram.unstructured(field, bins, pos)
146+
gamma_threads, counts_threads = gs_cy.variogram.unstructured(
147+
field, bins, pos, num_threads=2
148+
)
149+
150+
np.testing.assert_allclose(gamma_threads, gamma_default)
151+
np.testing.assert_array_equal(counts_threads, counts_default)
152+
153+
def test_unstructured_error_checks(self):
154+
pos = np.array(((0.0, 1.0), (0.0, 1.0)), dtype=np.double)
155+
field = np.array(((1.0, 2.0),), dtype=np.double)
156+
bins = np.array((0.0, 1.0), dtype=np.double)
157+
158+
with self.assertRaises(ValueError):
159+
gs_cy.variogram.unstructured(field[:, :1], bins, pos)
160+
161+
with self.assertRaises(ValueError):
162+
gs_cy.variogram.unstructured(field, np.array((0.0,), dtype=np.double), pos)
163+
164+
pos_bad = np.array(((0.0, 1.0), (0.0, 1.0), (0.0, 1.0)), dtype=np.double)
165+
with self.assertRaises(ValueError):
166+
gs_cy.variogram.unstructured(field, bins, pos_bad, distance_type="h")
167+
76168
def test_structured(self):
77169
z = np.array(
78170
(41.2, 40.2, 39.7, 39.2, 40.1, 38.3, 39.1, 40.0, 41.1, 40.3),

0 commit comments

Comments
 (0)