Skip to content

Commit cc8bb2a

Browse files
authored
Merge pull request #22 from simpeg/maintenance
Maintenance on pydiso
2 parents 60ad4a5 + 00c927d commit cc8bb2a

File tree

6 files changed

+187
-80
lines changed

6 files changed

+187
-80
lines changed

.github/make_ci_environ.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import tomllib
2+
from pathlib import Path
3+
import yaml
4+
import os
5+
6+
def parse_pyproject(path: str, optional_sections_to_skip=None):
7+
with open(path, "rb") as f:
8+
data = tomllib.load(f)
9+
10+
deps = set()
11+
12+
# project.dependencies (PEP 621)
13+
for dep in data.get("project", {}).get("dependencies", []):
14+
if "numpy" in dep:
15+
# numpy is also listed in build requirements with a higher version number
16+
# so we skip it here to avoid conflicts.
17+
continue
18+
deps.add(dep)
19+
20+
# optional dependencies (PEP 621)
21+
if optional_sections_to_skip is None:
22+
optional_sections_to_skip = []
23+
for group, group_deps in data.get("project", {}).get("optional-dependencies", {}).items():
24+
if group in optional_sections_to_skip:
25+
print("Skipping optional dependency group:", group)
26+
continue
27+
deps.update(group_deps)
28+
29+
deps.discard("geoana[all]")
30+
deps.discard("geoana[doc,all]")
31+
deps.discard("geoana[plot,extras,jittable]")
32+
33+
if "matplotlib" in deps:
34+
deps.remove("matplotlib")
35+
deps.add("matplotlib-base")
36+
return deps
37+
38+
def create_env_yaml(deps, name="env", python_version=None, free_threaded=False):
39+
conda_pkgs = []
40+
pip_pkgs = []
41+
42+
for dep in deps:
43+
# crude split: try to detect conda vs pip-only packages
44+
if any(dep.startswith(pip_only) for pip_only in ["git+", "http:", "https:", "file:"]):
45+
pip_pkgs.append(dep)
46+
else:
47+
conda_pkgs.append(dep)
48+
49+
dependencies = conda_pkgs
50+
if pip_pkgs:
51+
dependencies.append({"pip": pip_pkgs})
52+
53+
if python_version:
54+
if free_threaded:
55+
dependencies.insert(0, f"python-freethreading={python_version}")
56+
else:
57+
dependencies.insert(0, f"python={python_version}")
58+
59+
return {
60+
"name": name,
61+
"channels": ["conda-forge"],
62+
"dependencies": dependencies,
63+
}
64+
65+
if __name__ == "__main__":
66+
pyproject_path = Path("pyproject.toml")
67+
68+
py_vers = os.environ.get("PYTHON_VERSION", "3.11")
69+
mkl_vers = os.environ.get("MKL_VERSION", "2024")
70+
is_free_threaded = os.environ.get("FREE_THREADED", "false").lower() == "true"
71+
is_coverage = os.environ.get("DO_COVERAGE", "false").lower() == "true"
72+
env_name = os.environ.get("ENV_NAME", "pydiso-ci")
73+
74+
optional_to_skip = []
75+
if not is_coverage:
76+
optional_to_skip.append("coverage")
77+
78+
deps = parse_pyproject(pyproject_path, optional_sections_to_skip=optional_to_skip)
79+
deps.add("mkl-devel")
80+
deps.add("pkg-config")
81+
deps.add(f"mkl={mkl_vers}")
82+
env_data = create_env_yaml(deps, name=env_name, python_version=py_vers, free_threaded=is_free_threaded)
83+
84+
out_name = "environment_ci.yml"
85+
with open(out_name, "w") as f:
86+
yaml.safe_dump(env_data, f, sort_keys=False)
87+
88+
print("Generated environment_ci.yml with", len(deps), "dependencies")

.github/workflows/python-package-conda.yml

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,89 @@
11
name: Testing
22

3-
on:
4-
push:
5-
branches:
6-
- '*'
7-
tags:
8-
- 'v*'
9-
pull_request:
10-
branches:
11-
- '*'
12-
schedule:
13-
- cron: "0 13 * * 1"
3+
on: [push, pull_request]
4+
concurrency:
5+
group: ${{ github.workflow }}-${{ github.ref }}
6+
cancel-in-progress: true
147

158
jobs:
169
build-and-test:
17-
name: Testing (Python ${{ matrix.python-version }}, on ${{ matrix.os }}, with MKL ${{ matrix.mkl-version }})
10+
name: Testing (Python ${{ matrix.python-version }}${{ matrix.free-threaded && 't' || '' }}, on ${{ matrix.os }}, with MKL ${{ matrix.mkl-version }})
1811
runs-on: ${{ matrix.os }}
1912
defaults:
2013
run:
2114
shell: bash -l {0}
2215
strategy:
2316
fail-fast: False
2417
matrix:
25-
os: [ubuntu-latest, macos-12, windows-latest]
26-
python-version: ["3.10", "3.11", "3.12", "3.13"]
27-
mkl-version: ['2023', '2024']
18+
os: [ubuntu-latest, windows-latest]
19+
python-version: ["3.11", "3.12", "3.13", "3.14"]
20+
mkl-version: ['2024', '2025']
21+
free-threaded: [true, false]
2822
include:
2923
- os: ubuntu-latest
3024
python-version: "3.12"
31-
coverage: ${{ true }}
32-
exclude:
33-
- os: macos-12
3425
mkl-version: "2024"
26+
coverage: true
27+
- os: macos-15-intel
28+
python-version: "3.11"
29+
mkl-version: "2023"
30+
free-threaded:
31+
- os: macos-15-intel
32+
python-version: "3.12"
33+
mkl-version: "2023"
34+
free-threaded:
35+
exclude:
36+
- python-version: "3.11"
37+
free-threaded: true
38+
- python-version: "3.12"
39+
free-threaded: true
40+
- python-version: "3.13"
41+
free-threaded: true
42+
mkl-version: "2025"
43+
- python-version: "3.14"
44+
mkl-version: "2025"
3545

3646
steps:
37-
- uses: actions/checkout@v4
47+
- uses: actions/checkout@v5
48+
with:
49+
fetch-depth: 0
50+
51+
- name: Set up Python
52+
uses: actions/setup-python@v2
53+
with:
54+
python-version: "3.13"
55+
56+
- name: Create Conda environment file
57+
run: |
58+
python -m pip install pyyaml
59+
python .github/make_ci_environ.py
60+
env:
61+
PYTHON_VERSION: ${{ matrix.python-version}}
62+
MKL_VERSION: ${{ matrix.mkl-version }}
63+
FREE_THREADED: ${{ matrix.free-threaded && 'true' || 'false' }}
64+
DO_COVERAGE: ${{ matrix.coverage && 'true' || 'false' }}
65+
ENV_NAME: pydiso-ci
66+
3867
- name: Setup Conda
3968
uses: conda-incubator/setup-miniconda@v3
4069
with:
41-
python-version: ${{ matrix.python-version }}
42-
channels: conda-forge, defaults
43-
channel-priority: true
44-
activate-environment: dev
70+
auto-update-conda: true
71+
environment-file: environment_ci.yml
72+
activate-environment: pydiso-ci
4573

4674
- name: Conda information
4775
run: |
4876
conda info
4977
conda list
5078
conda config --show
5179
52-
- name: Create environment
53-
run: |
54-
conda install --quiet --yes -c conda-forge \
55-
pip numpy scipy cython mkl=${{ matrix.mkl-version }} pytest \
56-
mkl-devel pkg-config meson-python meson ninja setuptools_scm \
57-
${{ matrix.coverage && 'coverage' || ''}} \
58-
${{ matrix.os == 'windows-latest' && '"libblas=*=*mkl"' || ''}}
59-
6080
- name: Install Our Package
6181
run: |
6282
python -m pip install --no-build-isolation --verbose --editable . \
6383
--config-setting=compile-args=-v \
6484
${{ matrix.coverage && '--config-settings=setup-args="-Db_coverage=true"' || ''}} \
6585
${{ matrix.os == 'windows-latest' && '--config-settings=setup-args="-Dvsenv=true"' || ''}}
6686
67-
conda list
68-
6987
- name: Run Tests
7088
run: |
7189
${{ matrix.coverage && 'coverage run -m' || '' }} pytest -s -v

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ mkl_solver.c.dep
1010
coverage.xml
1111

1212
.idea/
13+
14+
.vscode/
15+
16+
environment_ci.yml

pydiso/_mkl_solver.pyx.in renamed to pydiso/_mkl_solver.in.pyx

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
#cython: language_level=3
1+
# cython: language_level=3
2+
# cython: embedsignature=True, language_level=3
3+
# cython: freethreading_compatible=True
24
cimport numpy as np
35
import cython
4-
from cpython.pythread cimport (
5-
PyThread_type_lock,
6-
PyThread_allocate_lock,
7-
PyThread_acquire_lock,
8-
PyThread_release_lock,
9-
PyThread_free_lock
10-
)
116

127
import numpy as np
138
import os
@@ -79,7 +74,6 @@ class PardisoError(Exception):
7974
class PardisoWarning(UserWarning):
8075
pass
8176

82-
8377
#call pardiso (pt, maxfct, mnum, mtype, phase, n, a, ia, ja, perm, nrhs, iparm, msglvl, b, x, error)
8478
cdef int mkl_progress(int *thread, int* step, char* stage, int stage_len) nogil:
8579
# must be a nogil process to pass to mkl pardiso progress reporting
@@ -175,7 +169,7 @@ ctypedef fused real_or_complex:
175169
{{for int_type in ["int_t", "long_t"]}}
176170
cdef class _PardisoHandle_{{int_type}}:
177171
cdef _MKL_DSS_HANDLE_t handle[64]
178-
cdef PyThread_type_lock lock
172+
cdef cython.pymutex lock
179173

180174
cdef {{int_type}} n, maxfct, mnum, msglvl
181175
cdef public {{int_type}} matrix_type
@@ -184,7 +178,6 @@ cdef class _PardisoHandle_{{int_type}}:
184178

185179
@cython.boundscheck(False)
186180
def __cinit__(self, A_dat_dtype, n, matrix_type, maxfct, mnum, msglvl):
187-
self.lock = PyThread_allocate_lock()
188181

189182
np_int_dtype = np.dtype(f"i{sizeof({{int_type}})}")
190183

@@ -197,11 +190,13 @@ cdef class _PardisoHandle_{{int_type}}:
197190
self.mnum = mnum
198191
self.msglvl = msglvl
199192

200-
if self.msglvl:
201-
#for reporting factorization progress via python's `print`
202-
mkl_set_progress(mkl_progress)
203-
else:
204-
mkl_set_progress(mkl_no_progress)
193+
194+
with self.lock:
195+
if self.msglvl:
196+
#for reporting factorization progress via python's `print`
197+
mkl_set_progress(mkl_progress)
198+
else:
199+
mkl_set_progress(mkl_no_progress)
205200

206201
is_single_precision = np.issubdtype(A_dat_dtype, np.single) or np.issubdtype(A_dat_dtype, np.csingle)
207202

@@ -264,14 +259,13 @@ cdef class _PardisoHandle_{{int_type}}:
264259
cdef {{int_type}} error, nrhs
265260
with nogil:
266261
nrhs = rhs.shape[1]
267-
PyThread_acquire_lock(self.lock, mode=1)
268-
pardiso{{if int_type == "long_t"}}_64{{endif}}(
269-
self.handle, &self.maxfct, &self.mnum, &self.matrix_type, &phase, &self.n,
270-
&a_data[0], &a_indptr[0], &a_indices[0], &self.perm[0],
271-
&nrhs, self.iparm, &self.msglvl,
272-
&rhs[0, 0], &out[0, 0], &error
273-
)
274-
PyThread_release_lock(self.lock)
262+
with self.lock:
263+
pardiso{{if int_type == "long_t"}}_64{{endif}}(
264+
self.handle, &self.maxfct, &self.mnum, &self.matrix_type, &phase, &self.n,
265+
&a_data[0], &a_indptr[0], &a_indices[0], &self.perm[0],
266+
&nrhs, self.iparm, &self.msglvl,
267+
&rhs[0, 0], &out[0, 0], &error
268+
)
275269
return error
276270

277271
@cython.boundscheck(False)
@@ -280,20 +274,15 @@ cdef class _PardisoHandle_{{int_type}}:
280274
cdef {{int_type}} phase = -1, nrhs = 0, error = 0
281275

282276
with nogil:
283-
PyThread_acquire_lock(self.lock, mode=1)
284-
if self._initialized():
285-
pardiso{{if int_type == "long_t"}}_64{{endif}}(
286-
self.handle, &self.maxfct, &self.mnum, &self.matrix_type,
287-
&phase, &self.n, NULL, NULL, NULL, NULL, &nrhs, self.iparm,
288-
&self.msglvl, NULL, NULL, &error)
289-
if error == 0:
290-
for i in range(64):
291-
self.handle[i] = NULL
292-
PyThread_release_lock(self.lock)
277+
with self.lock:
278+
if self._initialized():
279+
pardiso{{if int_type == "long_t"}}_64{{endif}}(
280+
self.handle, &self.maxfct, &self.mnum, &self.matrix_type,
281+
&phase, &self.n, NULL, NULL, NULL, NULL, &nrhs, self.iparm,
282+
&self.msglvl, NULL, NULL, &error)
283+
if error == 0:
284+
for i in range(64):
285+
self.handle[i] = NULL
293286
if error != 0:
294287
raise MemoryError("Pardiso Memory release error: " + _err_messages[error])
295-
if self.lock:
296-
#deallocate the lock
297-
PyThread_free_lock(self.lock)
298-
self.lock = NULL
299288
{{endfor}}

pydiso/meson.build

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
cython_file = custom_target(
2-
input: '_mkl_solver.pyx.in',
2+
input: '_mkl_solver.in.pyx',
33
output: '_mkl_solver.pyx',
44
command: [py,
55
'-c',
@@ -60,6 +60,11 @@ else
6060

6161
endif
6262

63+
cython_args = []
64+
if cy.version().version_compare('>=3.1.0')
65+
cython_args += ['-Xfreethreading_compatible=True']
66+
endif
67+
6368
c_undefined_ok = ['-Wno-maybe-uninitialized']
6469
cython_c_args = [numpy_nodepr_api, '-DCYTHON_CCOMPLEX=0']
6570

@@ -68,6 +73,7 @@ module_path = 'pydiso'
6873
py.extension_module(
6974
'_mkl_solver',
7075
cython_file,
76+
cython_args: cython_args,
7177
c_args: cython_c_args,
7278
install: true,
7379
subdir: module_path,

0 commit comments

Comments
 (0)