cvmatrix 3.2.3__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/.github/actions/release/action.yml +1 -1
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/.github/actions/test/action.yml +20 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/.github/workflows/pull_request_test_workflow.yml +7 -2
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/.github/workflows/test_workflow.yml +7 -2
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/CHANGELOG.md +14 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/CONTRIBUTING.md +45 -11
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/PKG-INFO +2 -1
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/cvmatrix/__init__.py +1 -1
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/cvmatrix/cvmatrix.py +258 -71
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/pyproject.toml +2 -1
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/tests/test_cvmatrix.py +175 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/uv.lock +38 -38
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/.github/actions/build/action.yml +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/.github/workflows/package_workflow.yml +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/.github/workflows/pull_request_package_workflow.yml +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/.gitignore +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/.readthedocs.yaml +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/LICENSE +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/README.md +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/benchmarks/README.md +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/benchmarks/__init__.py +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/benchmarks/benchmark.py +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/benchmarks/benchmark_cvmatrix.png +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/benchmarks/benchmark_cvmatrix_numpy_vs_jax.png +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/benchmarks/benchmark_cvmatrix_vs_naive.png +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/benchmarks/benchmark_jax_variants.png +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/benchmarks/benchmark_results.csv +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/benchmarks/benchmark_results_jax.csv +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/benchmarks/plot_benchmark.py +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/cvmatrix/partitioner.py +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/CONTRIBUTING.md +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/Makefile +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/README.md +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/api.rst +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/conf.py +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/index.rst +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/make.bat +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/requirements.txt +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/source/cvmatrix.rst +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/source/cvmatrix_module.rst +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/docs/source/partitioner_module.rst +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/examples/__init__.py +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/examples/training_matrices.py +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/examples/training_matrices_jax.py +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/tests/__init__.py +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/tests/load_data.py +0 -0
- {cvmatrix-3.2.3 → cvmatrix-3.3.0}/tests/naive_cvmatrix.py +0 -0
|
@@ -28,6 +28,26 @@ runs:
|
|
|
28
28
|
run: uv sync --group dev
|
|
29
29
|
shell: bash
|
|
30
30
|
|
|
31
|
+
# On free-threaded Python (e.g. 3.15t), importing an extension module that does not
|
|
32
|
+
# support free threading silently re-enables the GIL, and the tests would then pass
|
|
33
|
+
# without exercising free threading. Fail if any test dependency re-enables it.
|
|
34
|
+
- name: Check that the GIL stays disabled (free-threaded Python only)
|
|
35
|
+
if: ${{ endsWith(inputs.PYTHON_VERSION, 't') }}
|
|
36
|
+
run: |
|
|
37
|
+
uv run python - <<'EOF'
|
|
38
|
+
import importlib.util
|
|
39
|
+
import sys
|
|
40
|
+
|
|
41
|
+
import coverage, cvmatrix, numpy, pandas, pytest, typeguard # noqa: E401, F401
|
|
42
|
+
|
|
43
|
+
if importlib.util.find_spec("jax") is not None:
|
|
44
|
+
import jax.numpy # noqa: F401
|
|
45
|
+
if sys._is_gil_enabled():
|
|
46
|
+
sys.exit("The GIL was re-enabled by an imported extension module.")
|
|
47
|
+
print("The GIL stays disabled with all test dependencies imported.")
|
|
48
|
+
EOF
|
|
49
|
+
shell: bash
|
|
50
|
+
|
|
31
51
|
# The same test suite runs in both modes. With the JAX backend installed it also
|
|
32
52
|
# exercises the parametrized backend="jax" tests; without JAX those parametrizations
|
|
33
53
|
# are skipped (pytest.importorskip), verifying the numpy-only path.
|
|
@@ -18,7 +18,12 @@ jobs:
|
|
|
18
18
|
fail-fast: false
|
|
19
19
|
matrix:
|
|
20
20
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
21
|
-
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
|
|
21
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15", "3.15t"]
|
|
22
|
+
# jaxlib publishes no free-threaded (cp315t) wheel for Windows, and no sdist.
|
|
23
|
+
# Windows x 3.15t is still tested by the numpy-only job below.
|
|
24
|
+
exclude:
|
|
25
|
+
- os: windows-latest
|
|
26
|
+
python-version: "3.15t"
|
|
22
27
|
steps:
|
|
23
28
|
- uses: actions/checkout@v7
|
|
24
29
|
- uses: ./.github/actions/test
|
|
@@ -42,7 +47,7 @@ jobs:
|
|
|
42
47
|
fail-fast: false
|
|
43
48
|
matrix:
|
|
44
49
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
45
|
-
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
|
|
50
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15", "3.15t"]
|
|
46
51
|
steps:
|
|
47
52
|
- uses: actions/checkout@v7
|
|
48
53
|
- uses: ./.github/actions/test
|
|
@@ -18,7 +18,12 @@ jobs:
|
|
|
18
18
|
fail-fast: false
|
|
19
19
|
matrix:
|
|
20
20
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
21
|
-
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
21
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15", "3.15t"]
|
|
22
|
+
# jaxlib publishes no free-threaded (cp315t) wheel for Windows, and no sdist.
|
|
23
|
+
# Windows x 3.15t is still tested by the numpy-only job below.
|
|
24
|
+
exclude:
|
|
25
|
+
- os: windows-latest
|
|
26
|
+
python-version: "3.15t"
|
|
22
27
|
steps:
|
|
23
28
|
- uses: actions/checkout@v7
|
|
24
29
|
- uses: ./.github/actions/test
|
|
@@ -42,7 +47,7 @@ jobs:
|
|
|
42
47
|
fail-fast: false
|
|
43
48
|
matrix:
|
|
44
49
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
45
|
-
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
50
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15", "3.15t"]
|
|
46
51
|
steps:
|
|
47
52
|
- uses: actions/checkout@v7
|
|
48
53
|
- uses: ./.github/actions/test
|
|
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.3.0] - 2026-10-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `compute_XTX` and `compute_XTY` constructor parameters (both default `True`, the previous behavior) that control whether `fit` computes the dataset-wide `XTX` (`X^T W X`) and `XTY` (`X^T W Y`). A product that is never used can now be skipped: e.g., `compute_XTX=False` avoids the O(NK²) time and K² memory of `XTX` when only `training_XTY` and/or `training_statistics` are used, which can dominate `fit` for wide `X`. A product that is not computed is `None`, and the training methods that need it raise a `ValueError`. The products are still computed in `fit`, so they are pickled along with a fitted `CVMatrix` and are not recomputed per task when cross-validating in parallel worker processes (e.g., with `joblib`).
|
|
12
|
+
- Support for free-threaded Python. The CI test matrices now include free-threaded Python 3.15 (`3.15t`) on Linux, macOS, and Windows, with a check that no dependency re-enables the GIL. The JAX backend is tested on `3.15t` on Linux and macOS only, as jaxlib publishes no free-threaded wheel for Windows. The package declares the `Programming Language :: Python :: Free Threading :: 3 - Stable` classifier. A fitted `CVMatrix` can be shared by multiple threads, as the per-fold training methods only read it.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- The CI test workflow for pushes to `main` now also tests Python 3.15, matching the pull-request workflow.
|
|
16
|
+
- The per-fold training methods now gather only the validation set rows they use. Previously, the unweighted validation set rows of `X` were gathered, but not used, by `training_XTY` and `training_statistics`. With `weights` on the numpy backend, this makes `training_XTY` ~20% faster and `training_statistics` ~25-45% faster. Results are unchanged. Unweighted computations and computations traced by `jax.jit` were not affected.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- A fitted `CVMatrix` can be pickled with the standard library again. Since 3.2.0, the instance held the array namespace `xp` (the `numpy` or `jax.numpy` module), which cannot be pickled, so `pickle`, `copy.deepcopy`, `multiprocessing`, and `concurrent.futures.ProcessPoolExecutor` failed with `TypeError: cannot pickle 'module' object` (`joblib`'s default `loky` backend was not affected, as it pickles with `cloudpickle`). The module is now left out of the pickled state and resolved again from `backend` when unpickling. With `backend="jax"`, JAX's 64-bit mode is enabled before the arrays are restored, so float64 data stays float64 in fresh worker processes, where it would otherwise silently become float32.
|
|
20
|
+
- Re-fitting a `CVMatrix` now replaces all state derived from `Y` instead of keeping it from the previous fit. Re-fitting without `Y` resets `XTY`, `WY`, `sum_Y`, `sq_Y`, and `sum_sq_Y` to `None`, and re-fitting with weights that do not require `WY` (no `center_X`, `center_Y`, or `scale_Y`) resets `WY` to `None`; likewise, `sq_X` is reset to `None` when `scale_X` is `False`. Results were not affected, but the stale arrays were kept in memory and pickled along with the fitted model (e.g., to parallel worker processes).
|
|
21
|
+
|
|
8
22
|
## [3.2.3] - 2026-10-30
|
|
9
23
|
- Increased the maximum supported Python version from 3.14 to 3.15. Python 3.15 is now included in the CI test matrices. No functional changes.
|
|
10
24
|
|
|
@@ -30,18 +30,28 @@ The sections below outline the steps in each case.
|
|
|
30
30
|
1. (**Important**) Announce your plan to the rest of the community *before you start working*. This announcement should be in the form of a (new) issue;
|
|
31
31
|
2. (**Important**) Wait until some kind of consensus is reached about your idea being a good idea;
|
|
32
32
|
3. If needed, fork the repository to your own Github profile and create your own feature branch off of the latest main commit. While working on your feature branch, make sure to stay up to date with the main branch by pulling in changes, possibly from the 'upstream' repository (follow the instructions [here](https://help.github.com/articles/configuring-a-remote-for-a-fork/) and [here](https://help.github.com/articles/syncing-a-fork/));
|
|
33
|
-
4.
|
|
34
|
-
5.
|
|
35
|
-
6.
|
|
36
|
-
7. If
|
|
37
|
-
8.
|
|
38
|
-
9.
|
|
39
|
-
10.
|
|
40
|
-
11.
|
|
41
|
-
12.
|
|
33
|
+
4. Follow the [Design guidelines](#design-guidelines) when changing `CVMatrix`;
|
|
34
|
+
5. Make sure the existing tests still work by following the instructions in [Run the test suite](#run-the-test-suite);
|
|
35
|
+
6. Add your own tests (if necessary);
|
|
36
|
+
7. If you added your own tests, make sure they pass by following the instructions in [Run the test suite](#run-the-test-suite);
|
|
37
|
+
8. If your contribution is a performance enhancement, make sure to include benchmarks. See [Benchmarking](#benchmarking) for more information;
|
|
38
|
+
9. Update or expand the documentation;
|
|
39
|
+
10. Make sure the documentation builds without errors by following the instructions in [Build the documentation](#build-the-documentation);
|
|
40
|
+
11. Check that you can build the package locally and that it passes twine check. See [Build from source](#build-from-source) for more information;
|
|
41
|
+
12. [Push](http://rogerdudler.github.io/git-guide/) your feature branch to (your fork of) the CVMatrix repository on GitHub;
|
|
42
|
+
13. Create the pull request, e.g. following the instructions [here](https://help.github.com/articles/creating-a-pull-request/).
|
|
42
43
|
|
|
43
44
|
If you feel like you've made a valuable contribution, but you don't know how to write or run tests for it or generate the documentation, don't let this discourage you from making the pull request; we can help you! Just submit the pull request, but remember that you might be asked to append additional commits to your pull request.
|
|
44
45
|
|
|
46
|
+
## Design guidelines
|
|
47
|
+
|
|
48
|
+
`CVMatrix` is used in several execution settings: traced by `jax.jit`/`jax.vmap` with `backend="jax"`, called concurrently from multiple threads (including on free-threaded Python, which has no GIL), and pickled to worker processes (e.g., with `joblib`, `multiprocessing`, or `concurrent.futures.ProcessPoolExecutor`). To keep it correct and fast in all of them:
|
|
49
|
+
|
|
50
|
+
- **The per-fold methods only read the fitted state.** `training_XTX`, `training_XTY`, `training_XTX_XTY`, and `training_statistics` must not modify the instance, e.g., by caching results on it. Compute anything dataset-wide in `fit`. A cache written during a per-fold call would leak tracers under `jax.jit`, race between threads, and, in worker processes, be filled in a throwaway copy, i.e., recomputed for every task.
|
|
51
|
+
- **Route array operations through `self.xp`**, not `np`, so that the NumPy and JAX backends share one implementation and the per-fold methods stay traceable. Data-dependent checks that cannot run under tracing are skipped for tracer inputs (see `_TRACER_TYPES`). The NumPy backend's results must not change.
|
|
52
|
+
- **Keep instances picklable with the standard library's `pickle`.** Do not store modules, locks, open files, or other unpicklable objects on the instance. If one is unavoidable, leave it out in `__reduce__` and restore it in `_reconstruct`, as is done for the array namespace `xp`. `test_pickle` and `test_process_pool` check this.
|
|
53
|
+
- **Test both backends.** Every test runs with both `backend="numpy"` and `backend="jax"` through the `_set_backend` fixture in `tests/test_cvmatrix.py`; JAX-only tests skip themselves under the NumPy parametrization.
|
|
54
|
+
|
|
45
55
|
## Build from source
|
|
46
56
|
|
|
47
57
|
CVMatrix uses [uv](https://docs.astral.sh/uv/) to manage its dependencies and packaging. To build the package from source, follow these steps:
|
|
@@ -101,9 +111,25 @@ To run the test suite, follow these steps:
|
|
|
101
111
|
3. Now, the tests can be run with the following command:
|
|
102
112
|
|
|
103
113
|
```shell
|
|
104
|
-
uv run pytest tests --doctest-modules
|
|
114
|
+
uv run pytest tests --doctest-modules
|
|
105
115
|
```
|
|
106
116
|
|
|
117
|
+
The pytest options in `pyproject.toml` enable runtime type checking with [typeguard](https://typeguard.readthedocs.io/) (`--typeguard-packages=cvmatrix`) and coverage reports. Note that `--typeguard-packages` takes the package name `cvmatrix`, not a path: `cvmatrix/` silently instruments nothing.
|
|
118
|
+
|
|
119
|
+
### Test on free-threaded Python
|
|
120
|
+
|
|
121
|
+
CVMatrix also runs on free-threaded builds of Python (e.g., `3.15t`), which have no GIL. To run the test suite on one without touching your default environment, let uv create a temporary, isolated environment:
|
|
122
|
+
|
|
123
|
+
```shell
|
|
124
|
+
uv run --isolated --python 3.15t --extra jax --group dev pytest tests --doctest-modules
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A free-threaded interpreter silently re-enables the GIL if it imports an extension module that does not support free threading. Check that the GIL stays disabled with all dependencies imported; this must print `False`:
|
|
128
|
+
|
|
129
|
+
```shell
|
|
130
|
+
uv run --isolated --python 3.15t --extra jax python -c "import sys, cvmatrix, jax; print(sys._is_gil_enabled())"
|
|
131
|
+
```
|
|
132
|
+
|
|
107
133
|
## Build the documentation
|
|
108
134
|
|
|
109
135
|
1. Install sphinx, the sphinx-rtd-theme, and the MyST-Parser:
|
|
@@ -126,4 +152,12 @@ To run the test suite, follow these steps:
|
|
|
126
152
|
|
|
127
153
|
## Benchmarking
|
|
128
154
|
|
|
129
|
-
To run benchmarks, follow the instructions [here](https://github.com/Sm00thix/CVMatrix/blob/main/benchmarks/README.md).
|
|
155
|
+
To run benchmarks, follow the instructions [here](https://github.com/Sm00thix/CVMatrix/blob/main/benchmarks/README.md).
|
|
156
|
+
|
|
157
|
+
`benchmarks/benchmark.py` can be configured with environment variables, e.g., `BENCH_CONFIGS=plot` (only the three preprocessing combinations in the plots), `BENCH_PS=3,100,10000` (the numbers of folds), `BENCH_N`/`BENCH_K`/`BENCH_M` (the data size), and `BENCH_CSV` (the output file):
|
|
158
|
+
|
|
159
|
+
```shell
|
|
160
|
+
OPENBLAS_NUM_THREADS=1 BENCH_CONFIGS=plot BENCH_PS=3,100,10000 BENCH_CSV=my_results.csv uv run python benchmarks/benchmark.py
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
When you compare the performance of your change against `main`, run both versions on the same machine with a single BLAS thread, as above, and alternate between them over several rounds, also with the order reversed. The per-fold timings depend on the memory allocation history of the process, so identical code can differ by several percent between runs, and a fixed run order can look like a consistent difference.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cvmatrix
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: Fast computation of possibly weighted and possibly centered/scaled training set kernel matrices in a cross-validation setting.
|
|
5
5
|
Project-URL: Homepage, https://cvmatrix.readthedocs.io/en/latest/
|
|
6
6
|
Project-URL: Repository, https://github.com/Sm00thix/CVMatrix
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.15
|
|
17
|
+
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
17
18
|
Requires-Python: <3.16,>=3.10
|
|
18
19
|
Requires-Dist: numpy<3.0.0,>=2.0.0
|
|
19
20
|
Provides-Extra: jax
|
|
@@ -100,6 +100,37 @@ def _resolve_backend(backend: str):
|
|
|
100
100
|
raise ValueError(f"Invalid backend: {backend!r}. Must be 'numpy' or 'jax'.")
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
def _reconstruct(cls: type, backend: str, dtype: FloatDType) -> CVMatrix:
|
|
104
|
+
"""
|
|
105
|
+
Creates an empty instance of `cls`, a `CVMatrix` or a subclass, with its array
|
|
106
|
+
backend set up. Used by `CVMatrix.__reduce__` for unpickling: pickle calls it before
|
|
107
|
+
restoring the pickled arrays, so that, with the jax backend and a 64-bit `dtype`,
|
|
108
|
+
JAX's 64-bit mode is enabled before the arrays are rebuilt. Otherwise, float64
|
|
109
|
+
arrays would silently become float32 in a fresh process, e.g., a worker process.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
cls : type
|
|
114
|
+
The class of the pickled instance.
|
|
115
|
+
|
|
116
|
+
backend : {"numpy", "jax"}
|
|
117
|
+
The array backend of the pickled instance.
|
|
118
|
+
|
|
119
|
+
dtype : type[np.floating]
|
|
120
|
+
The data type of the pickled instance.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
CVMatrix
|
|
125
|
+
An instance of `cls` with `backend`, `dtype`, and `xp` set.
|
|
126
|
+
"""
|
|
127
|
+
obj = cls.__new__(cls)
|
|
128
|
+
obj.backend = backend
|
|
129
|
+
obj.dtype = dtype
|
|
130
|
+
obj._init_backend()
|
|
131
|
+
return obj
|
|
132
|
+
|
|
133
|
+
|
|
103
134
|
class CVMatrix:
|
|
104
135
|
r"""
|
|
105
136
|
Implements the fast cross-validation algorithms for kernel matrix-based models such
|
|
@@ -156,6 +187,31 @@ class CVMatrix:
|
|
|
156
187
|
input arrays are not NumPy arrays of type `dtype`, then a copy is made. If
|
|
157
188
|
`True` a copy is always made. If no copy is made, then external modifications
|
|
158
189
|
to `X` or `Y` will result in undefined behavior.
|
|
190
|
+
|
|
191
|
+
backend : {"numpy", "jax"}, optional, default="numpy"
|
|
192
|
+
The array backend used for the computations. `"numpy"` uses NumPy, which is
|
|
193
|
+
the only required dependency. `"jax"` uses `jax.numpy` so that the per-fold
|
|
194
|
+
computations of `training_XTX`, `training_XTY`, `training_XTX_XTY`, and
|
|
195
|
+
`training_statistics` can be traced by `jax.jit` and batched over folds with
|
|
196
|
+
`jax.vmap` on CPU, GPU, or TPU. It requires the optional JAX dependency, which
|
|
197
|
+
is installed with `pip install cvmatrix[jax]`; otherwise, an `ImportError` is
|
|
198
|
+
raised. If `"jax"` is used with a 64-bit `dtype`, JAX's 64-bit mode
|
|
199
|
+
(`jax_enable_x64`) is enabled globally so that `dtype` is honored instead of
|
|
200
|
+
being truncated to 32 bits.
|
|
201
|
+
|
|
202
|
+
compute_XTX : bool, optional, default=True
|
|
203
|
+
Whether `fit` computes the dataset-wide
|
|
204
|
+
:math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{X}`. Its cost grows with the
|
|
205
|
+
square of the number of predictor variables in `X`, so for wide `X` it can
|
|
206
|
+
dominate `fit`. Set this to `False` if neither `training_XTX` nor
|
|
207
|
+
`training_XTX_XTY` will be called. If `False`, these methods raise a
|
|
208
|
+
`ValueError`.
|
|
209
|
+
|
|
210
|
+
compute_XTY : bool, optional, default=True
|
|
211
|
+
Whether `fit` computes the dataset-wide
|
|
212
|
+
:math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{Y}` when `Y` is provided. Set
|
|
213
|
+
this to `False` if neither `training_XTY` nor `training_XTX_XTY` will be
|
|
214
|
+
called. If `False`, these methods raise a `ValueError`.
|
|
159
215
|
"""
|
|
160
216
|
|
|
161
217
|
def __init__(
|
|
@@ -168,6 +224,8 @@ class CVMatrix:
|
|
|
168
224
|
dtype: FloatDType = np.float64,
|
|
169
225
|
copy: bool = True,
|
|
170
226
|
backend: Literal["numpy", "jax"] = "numpy",
|
|
227
|
+
compute_XTX: bool = True,
|
|
228
|
+
compute_XTY: bool = True,
|
|
171
229
|
) -> None:
|
|
172
230
|
self.center_X = center_X
|
|
173
231
|
self.center_Y = center_Y
|
|
@@ -176,18 +234,10 @@ class CVMatrix:
|
|
|
176
234
|
self.ddof = ddof
|
|
177
235
|
self.dtype = dtype.type if isinstance(dtype, np.dtype) else dtype
|
|
178
236
|
self.copy = copy
|
|
237
|
+
self.compute_XTX = compute_XTX
|
|
238
|
+
self.compute_XTY = compute_XTY
|
|
179
239
|
self.backend = backend
|
|
180
|
-
|
|
181
|
-
# through `self.xp` so that, with backend="jax", the per-fold `training_*`
|
|
182
|
-
# methods can be traced by jax.jit/jax.vmap. The numpy backend is byte-identical
|
|
183
|
-
# to previous releases.
|
|
184
|
-
self.xp = _resolve_backend(backend)
|
|
185
|
-
if backend == "jax" and np.dtype(self.dtype).itemsize >= 8:
|
|
186
|
-
# JAX defaults to 32-bit; enable 64-bit precision so a requested float64
|
|
187
|
-
# dtype is honored instead of being silently truncated to float32.
|
|
188
|
-
import jax
|
|
189
|
-
|
|
190
|
-
jax.config.update("jax_enable_x64", True)
|
|
240
|
+
self._init_backend()
|
|
191
241
|
self.resolution = np.finfo(dtype).resolution * 10
|
|
192
242
|
self.X = None
|
|
193
243
|
self.Y = None
|
|
@@ -208,6 +258,18 @@ class CVMatrix:
|
|
|
208
258
|
self.sum_w = None
|
|
209
259
|
self.num_nonzero_w = None
|
|
210
260
|
|
|
261
|
+
def __reduce__(self) -> tuple:
|
|
262
|
+
"""
|
|
263
|
+
Supports pickling with the standard library, e.g., to send a fitted `CVMatrix`
|
|
264
|
+
to worker processes with `multiprocessing` or
|
|
265
|
+
`concurrent.futures.ProcessPoolExecutor`. The array namespace `xp` is a module,
|
|
266
|
+
which cannot be pickled, so it is left out of the pickled state and resolved
|
|
267
|
+
again from `backend` by `_reconstruct` before the state is restored.
|
|
268
|
+
"""
|
|
269
|
+
state = self.__dict__.copy()
|
|
270
|
+
del state["xp"]
|
|
271
|
+
return _reconstruct, (type(self), self.backend, self.dtype), state
|
|
272
|
+
|
|
211
273
|
def fit(
|
|
212
274
|
self,
|
|
213
275
|
X: npt.ArrayLike,
|
|
@@ -216,8 +278,9 @@ class CVMatrix:
|
|
|
216
278
|
) -> None:
|
|
217
279
|
r"""
|
|
218
280
|
Loads and stores `X`, `Y`, and "weights", for cross-validation. Computes
|
|
219
|
-
dataset-wide :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{X}`
|
|
220
|
-
not `None
|
|
281
|
+
dataset-wide :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{X}` if
|
|
282
|
+
`compute_XTX` is `True` and, if `Y` is not `None` and `compute_XTY` is `True`,
|
|
283
|
+
:math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{Y}`. If `center_X`,
|
|
221
284
|
`center_Y`, `scale_X`, or `scale_Y` is `True`, the corresponding global
|
|
222
285
|
statistics are also computed.
|
|
223
286
|
|
|
@@ -252,13 +315,14 @@ class CVMatrix:
|
|
|
252
315
|
M : int or None
|
|
253
316
|
The number of response variables in `Y`. If `Y` is `None`, this is `None`.
|
|
254
317
|
|
|
255
|
-
XTX : Array
|
|
318
|
+
XTX : Array or None
|
|
256
319
|
The total matrix :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{X}` for the
|
|
257
|
-
entire dataset.
|
|
320
|
+
entire dataset. This is computed only if `compute_XTX` is `True`.
|
|
258
321
|
|
|
259
322
|
XTY : Array or None
|
|
260
323
|
The total matrix :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{Y}` for the
|
|
261
|
-
entire dataset. This is computed only if `Y` is not `None
|
|
324
|
+
entire dataset. This is computed only if `Y` is not `None` and
|
|
325
|
+
`compute_XTY` is `True`.
|
|
262
326
|
|
|
263
327
|
sum_X : Array or None
|
|
264
328
|
The row of column-wise weighted sums of `X` for the entire dataset. This is
|
|
@@ -362,6 +426,7 @@ class CVMatrix:
|
|
|
362
426
|
Raises
|
|
363
427
|
------
|
|
364
428
|
ValueError
|
|
429
|
+
- If `fit` did not compute `XTX` because `compute_XTX` was `False`.
|
|
365
430
|
- If `self.center_X` or `self.scale_X` is `True` but cannot be computed due
|
|
366
431
|
to the training set having no non-zero weights.
|
|
367
432
|
- If `self.scale_X` is `True` and the corresponding weighted standard
|
|
@@ -426,6 +491,7 @@ class CVMatrix:
|
|
|
426
491
|
------
|
|
427
492
|
ValueError
|
|
428
493
|
- If `Y` is `None`.
|
|
494
|
+
- If `fit` did not compute `XTY` because `compute_XTY` was `False`.
|
|
429
495
|
- If `self.center_X`, `self.center_Y`, `self.scale_X`, or `self.scale_Y` is
|
|
430
496
|
`True` and the corresponding weighted means and standard deviations
|
|
431
497
|
cannot be computed due to the training set having no non-zero
|
|
@@ -496,6 +562,8 @@ class CVMatrix:
|
|
|
496
562
|
------
|
|
497
563
|
ValueError
|
|
498
564
|
- If `Y` is `None`.
|
|
565
|
+
- If `fit` did not compute `XTX` because `compute_XTX` was `False`.
|
|
566
|
+
- If `fit` did not compute `XTY` because `compute_XTY` was `False`.
|
|
499
567
|
- If `self.center_X`, `self.center_Y`, `self.scale_X`, or `self.scale_Y` is
|
|
500
568
|
`True` and the corresponding weighted means and standard deviations
|
|
501
569
|
cannot be computed due to the training set having no non-zero
|
|
@@ -562,16 +630,22 @@ class CVMatrix:
|
|
|
562
630
|
set.
|
|
563
631
|
|
|
564
632
|
"""
|
|
565
|
-
|
|
566
|
-
|
|
633
|
+
return_X_mean = self.center_X or self.scale_X
|
|
634
|
+
return_Y_mean = (self.center_Y or self.scale_Y) and self.Y is not None
|
|
635
|
+
X_val, _, Y_val, _ = self._get_val_matrices(
|
|
636
|
+
val_indices=validation_indices,
|
|
637
|
+
return_X_val=return_X_mean,
|
|
638
|
+
return_X_val_unweighted=False,
|
|
639
|
+
return_Y_val=return_Y_mean,
|
|
640
|
+
return_Y_val_unweighted=False,
|
|
567
641
|
)
|
|
568
642
|
return self._compute_training_stats(
|
|
569
643
|
val_indices=validation_indices,
|
|
570
644
|
X_val=X_val,
|
|
571
645
|
Y_val=Y_val,
|
|
572
|
-
return_X_mean=
|
|
646
|
+
return_X_mean=return_X_mean,
|
|
573
647
|
return_X_std=self.scale_X,
|
|
574
|
-
return_Y_mean=
|
|
648
|
+
return_Y_mean=return_Y_mean,
|
|
575
649
|
return_Y_std=self.scale_Y and self.Y is not None,
|
|
576
650
|
)[:-1] # Exclude the sum of training weights from the return tuple
|
|
577
651
|
|
|
@@ -775,11 +849,11 @@ class CVMatrix:
|
|
|
775
849
|
Whether to return the training set
|
|
776
850
|
:math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{X}`.
|
|
777
851
|
|
|
778
|
-
return_XTY : bool
|
|
852
|
+
return_XTY : bool
|
|
779
853
|
Whether to return the training set
|
|
780
854
|
:math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{Y}`.
|
|
781
855
|
|
|
782
|
-
|
|
856
|
+
val_indices : IndexArray
|
|
783
857
|
An integer array of indices for the validation set for which to
|
|
784
858
|
return the corresponding training set
|
|
785
859
|
:math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{X}` and
|
|
@@ -802,8 +876,10 @@ class CVMatrix:
|
|
|
802
876
|
Raises
|
|
803
877
|
------
|
|
804
878
|
ValueError
|
|
805
|
-
If both `return_XTX` and `return_XTY` are `False
|
|
806
|
-
`True` and `Y` is `None
|
|
879
|
+
If both `return_XTX` and `return_XTY` are `False`, if `return_XTY` is
|
|
880
|
+
`True` and `Y` is `None`, if `return_XTX` is `True` and `fit` did not
|
|
881
|
+
compute `XTX` because `compute_XTX` was `False`, or if `return_XTY` is
|
|
882
|
+
`True` and `fit` did not compute `XTY` because `compute_XTY` was `False`.
|
|
807
883
|
"""
|
|
808
884
|
if not return_XTX and not return_XTY:
|
|
809
885
|
raise ValueError(
|
|
@@ -811,8 +887,28 @@ class CVMatrix:
|
|
|
811
887
|
)
|
|
812
888
|
if return_XTY and self.Y is None:
|
|
813
889
|
raise ValueError("Response variables `Y` are not provided.")
|
|
890
|
+
# Check the fitted products rather than the flags, so that the flags cannot
|
|
891
|
+
# disagree with what `fit` actually computed.
|
|
892
|
+
if return_XTX and self.XTX is None:
|
|
893
|
+
raise ValueError(
|
|
894
|
+
"`XTX` was not computed by `fit`. Fit a CVMatrix constructed with "
|
|
895
|
+
"`compute_XTX=True` to use `training_XTX` or `training_XTX_XTY`."
|
|
896
|
+
)
|
|
897
|
+
if return_XTY and self.XTY is None:
|
|
898
|
+
raise ValueError(
|
|
899
|
+
"`XTY` was not computed by `fit`. Fit a CVMatrix constructed with "
|
|
900
|
+
"`compute_XTY=True` to use `training_XTY` or `training_XTX_XTY`."
|
|
901
|
+
)
|
|
902
|
+
# The weighted `Y_val` feeds the weighted mean and standard deviation of `Y`,
|
|
903
|
+
# which centering (`center_X` or `center_Y`) and scaling (`scale_Y`) of `XTY`
|
|
904
|
+
# use.
|
|
905
|
+
return_Y_val = return_XTY and self._needs_Y_stats
|
|
814
906
|
X_val, X_val_unweighted, Y_val, Y_val_unweighted = self._get_val_matrices(
|
|
815
|
-
val_indices=val_indices,
|
|
907
|
+
val_indices=val_indices,
|
|
908
|
+
return_X_val=True,
|
|
909
|
+
return_X_val_unweighted=return_XTX,
|
|
910
|
+
return_Y_val=return_Y_val,
|
|
911
|
+
return_Y_val_unweighted=return_XTY,
|
|
816
912
|
)
|
|
817
913
|
X_train_mean, X_train_std, Y_train_mean, Y_train_std, sum_w_train = (
|
|
818
914
|
self._compute_training_stats(
|
|
@@ -822,11 +918,7 @@ class CVMatrix:
|
|
|
822
918
|
if self.center_X or self.scale_X or (return_XTY and self.center_Y)
|
|
823
919
|
else None
|
|
824
920
|
),
|
|
825
|
-
Y_val=
|
|
826
|
-
Y_val
|
|
827
|
-
if return_XTY and (self.center_X or self.center_Y or self.scale_Y)
|
|
828
|
-
else None
|
|
829
|
-
),
|
|
921
|
+
Y_val=Y_val,
|
|
830
922
|
return_X_mean=self.center_X or (return_XTY and self.center_Y),
|
|
831
923
|
return_Y_mean=return_XTY and (self.center_X or self.center_Y),
|
|
832
924
|
return_X_std=self.scale_X,
|
|
@@ -898,50 +990,106 @@ class CVMatrix:
|
|
|
898
990
|
)
|
|
899
991
|
|
|
900
992
|
def _get_val_matrices(
|
|
901
|
-
self,
|
|
993
|
+
self,
|
|
994
|
+
val_indices: IndexArray,
|
|
995
|
+
*,
|
|
996
|
+
return_X_val: bool,
|
|
997
|
+
return_X_val_unweighted: bool,
|
|
998
|
+
return_Y_val: bool,
|
|
999
|
+
return_Y_val_unweighted: bool,
|
|
902
1000
|
) -> Tuple[
|
|
903
|
-
Array,
|
|
904
|
-
Array,
|
|
1001
|
+
Optional[Array],
|
|
1002
|
+
Optional[Array],
|
|
905
1003
|
Optional[Array],
|
|
906
1004
|
Optional[Array],
|
|
907
1005
|
]:
|
|
908
1006
|
"""
|
|
909
|
-
Returns the validation set matrices for a given fold.
|
|
1007
|
+
Returns the requested validation set matrices for a given fold. Only the
|
|
1008
|
+
requested matrices are gathered, as gathering the validation set rows is a
|
|
1009
|
+
significant part of the cost of each fold.
|
|
1010
|
+
|
|
910
1011
|
Parameters
|
|
911
1012
|
----------
|
|
912
1013
|
val_indices : Array of shape (N_val,)
|
|
913
1014
|
The indices of the validation set samples for the given fold.
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
the
|
|
1015
|
+
|
|
1016
|
+
return_X_val : bool
|
|
1017
|
+
Whether to return the validation set of weighted predictor variables.
|
|
1018
|
+
|
|
1019
|
+
return_X_val_unweighted : bool
|
|
1020
|
+
Whether to return the validation set of unweighted predictor variables.
|
|
1021
|
+
|
|
1022
|
+
return_Y_val : bool
|
|
1023
|
+
Whether to return the validation set of response variables, weighted if
|
|
1024
|
+
`WY` is computed and unweighted otherwise.
|
|
1025
|
+
|
|
1026
|
+
return_Y_val_unweighted : bool
|
|
1027
|
+
Whether to return the validation set of unweighted response variables.
|
|
1028
|
+
|
|
917
1029
|
Returns
|
|
918
1030
|
-------
|
|
919
|
-
Tuple of
|
|
920
|
-
The validation
|
|
921
|
-
unweighted predictor variables `
|
|
922
|
-
response variables `
|
|
923
|
-
variables `
|
|
924
|
-
|
|
1031
|
+
Tuple of four elements of Optional[Array]
|
|
1032
|
+
The validation sets of predictor variables `X_val` of shape (N_val, K),
|
|
1033
|
+
unweighted predictor variables `X_val_unweighted` of shape (N_val, K),
|
|
1034
|
+
response variables `Y_val` of shape (N_val, M), and unweighted response
|
|
1035
|
+
variables `Y_val_unweighted` of shape (N_val, M). A matrix that is not
|
|
1036
|
+
requested is `None`.
|
|
925
1037
|
"""
|
|
926
|
-
X_val = self.
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
if return_XTY:
|
|
932
|
-
if self.weights is None or not (
|
|
933
|
-
self.center_X or self.center_Y or self.scale_Y
|
|
934
|
-
):
|
|
935
|
-
Y_val = self.Y[val_indices]
|
|
936
|
-
Y_val_unweighted = Y_val
|
|
937
|
-
else:
|
|
938
|
-
Y_val = self.WY[val_indices]
|
|
939
|
-
Y_val_unweighted = self.Y[val_indices]
|
|
1038
|
+
X_val, X_val_unweighted = self._gather_val_pair(
|
|
1039
|
+
val_indices, self.WX, self.X, return_X_val, return_X_val_unweighted
|
|
1040
|
+
)
|
|
1041
|
+
if self.weights is None or not self._needs_Y_stats:
|
|
1042
|
+
Y_maybe_weighted = self.Y
|
|
940
1043
|
else:
|
|
941
|
-
|
|
942
|
-
|
|
1044
|
+
Y_maybe_weighted = self.WY
|
|
1045
|
+
Y_val, Y_val_unweighted = self._gather_val_pair(
|
|
1046
|
+
val_indices, Y_maybe_weighted, self.Y, return_Y_val, return_Y_val_unweighted
|
|
1047
|
+
)
|
|
943
1048
|
return X_val, X_val_unweighted, Y_val, Y_val_unweighted
|
|
944
1049
|
|
|
1050
|
+
@staticmethod
|
|
1051
|
+
def _gather_val_pair(
|
|
1052
|
+
val_indices: IndexArray,
|
|
1053
|
+
mat: Optional[Array],
|
|
1054
|
+
mat_unweighted: Optional[Array],
|
|
1055
|
+
return_val: bool,
|
|
1056
|
+
return_val_unweighted: bool,
|
|
1057
|
+
) -> Tuple[Optional[Array], Optional[Array]]:
|
|
1058
|
+
"""
|
|
1059
|
+
Gathers the requested validation set rows of a possibly weighted matrix and of
|
|
1060
|
+
its unweighted counterpart. If both are requested and they are the same array
|
|
1061
|
+
(e.g., if no weights are used), the rows are gathered only once.
|
|
1062
|
+
|
|
1063
|
+
Parameters
|
|
1064
|
+
----------
|
|
1065
|
+
val_indices : Array of shape (N_val,)
|
|
1066
|
+
The indices of the validation set samples for the given fold.
|
|
1067
|
+
|
|
1068
|
+
mat : None or Array of shape (N, K) or (N, M)
|
|
1069
|
+
The possibly weighted matrix, `WX` or `WY`.
|
|
1070
|
+
|
|
1071
|
+
mat_unweighted : None or Array of shape (N, K) or (N, M)
|
|
1072
|
+
The unweighted matrix, `X` or `Y`.
|
|
1073
|
+
|
|
1074
|
+
return_val : bool
|
|
1075
|
+
Whether to return the validation set rows of `mat`.
|
|
1076
|
+
|
|
1077
|
+
return_val_unweighted : bool
|
|
1078
|
+
Whether to return the validation set rows of `mat_unweighted`.
|
|
1079
|
+
|
|
1080
|
+
Returns
|
|
1081
|
+
-------
|
|
1082
|
+
Tuple of two elements of Optional[Array]
|
|
1083
|
+
The validation set rows of `mat` and of `mat_unweighted`. A matrix that is
|
|
1084
|
+
not requested is `None`.
|
|
1085
|
+
"""
|
|
1086
|
+
mat_val = mat[val_indices] if return_val else None
|
|
1087
|
+
if not return_val_unweighted:
|
|
1088
|
+
return mat_val, None
|
|
1089
|
+
if mat_val is not None and mat is mat_unweighted:
|
|
1090
|
+
return mat_val, mat_val
|
|
1091
|
+
return mat_val, mat_unweighted[val_indices]
|
|
1092
|
+
|
|
945
1093
|
def _training_kernel_matrix(
|
|
946
1094
|
self,
|
|
947
1095
|
total_kernel_mat: Array,
|
|
@@ -1130,6 +1278,21 @@ class CVMatrix:
|
|
|
1130
1278
|
mat_train_std = xp.where(mat_train_std <= self.resolution, 1, mat_train_std)
|
|
1131
1279
|
return mat_train_std
|
|
1132
1280
|
|
|
1281
|
+
def _init_backend(self) -> None:
|
|
1282
|
+
"""
|
|
1283
|
+
Resolves the array namespace `xp` from `backend`: numpy (the default) or
|
|
1284
|
+
jax.numpy. All array operations are routed through `xp` so that, with the jax
|
|
1285
|
+
backend, the per-fold `training_*` methods can be traced by jax.jit/jax.vmap.
|
|
1286
|
+
The numpy backend is byte-identical to previous releases. JAX defaults to 32
|
|
1287
|
+
bits, so with the jax backend and a 64-bit `dtype`, JAX's 64-bit mode is enabled
|
|
1288
|
+
so that `dtype` is honored instead of being silently truncated to float32.
|
|
1289
|
+
"""
|
|
1290
|
+
self.xp = _resolve_backend(self.backend)
|
|
1291
|
+
if self.backend == "jax" and np.dtype(self.dtype).itemsize >= 8:
|
|
1292
|
+
import jax
|
|
1293
|
+
|
|
1294
|
+
jax.config.update("jax_enable_x64", True)
|
|
1295
|
+
|
|
1133
1296
|
def _init_mat(self, mat: Array) -> Array:
|
|
1134
1297
|
"""
|
|
1135
1298
|
Casts the matrix to the dtype specified in the constructor and reshapes it if
|
|
@@ -1192,35 +1355,55 @@ class CVMatrix:
|
|
|
1192
1355
|
else:
|
|
1193
1356
|
self.weights = None
|
|
1194
1357
|
|
|
1358
|
+
@property
|
|
1359
|
+
def _needs_Y_stats(self) -> bool:
|
|
1360
|
+
r"""
|
|
1361
|
+
Whether the training set :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{Y}`
|
|
1362
|
+
uses the weighted statistics of `Y`: the row of column-wise weighted means for
|
|
1363
|
+
centering (`center_X` or `center_Y`) and, additionally, the row of column-wise
|
|
1364
|
+
weighted standard deviations for scaling (`scale_Y`). Only then are `WY` (with
|
|
1365
|
+
weights) and `sum_Y` computed and the weighted validation set rows of `Y`
|
|
1366
|
+
gathered.
|
|
1367
|
+
"""
|
|
1368
|
+
return self.center_X or self.center_Y or self.scale_Y
|
|
1369
|
+
|
|
1195
1370
|
def _init_weighted_mats(self):
|
|
1196
1371
|
"""
|
|
1197
|
-
Initializes the weighted matrices `WX` and `WY
|
|
1198
|
-
|
|
1199
|
-
`
|
|
1200
|
-
|
|
1372
|
+
Initializes the weighted matrices `WX` and `WY`. Without weights, they are `X`
|
|
1373
|
+
and `Y` themselves. With weights, they are computed as the product of `X` and
|
|
1374
|
+
`Y` with the `weights`, where `WY` is computed only if `Y` is not `None` and its
|
|
1375
|
+
weighted statistics are needed (see `_needs_Y_stats`). If `WY` is not
|
|
1376
|
+
computed, it is set to `None` so that no `WY` from a previous fit is kept.
|
|
1201
1377
|
"""
|
|
1202
1378
|
if self.weights is None:
|
|
1203
1379
|
self.WX = self.X
|
|
1204
|
-
|
|
1205
|
-
self.WY = self.Y
|
|
1380
|
+
self.WY = self.Y
|
|
1206
1381
|
else:
|
|
1207
1382
|
self.WX = self.X * self.weights
|
|
1208
|
-
if self.Y is not None and
|
|
1383
|
+
if self.Y is not None and self._needs_Y_stats:
|
|
1209
1384
|
self.WY = self.Y * self.weights
|
|
1385
|
+
else:
|
|
1386
|
+
self.WY = None
|
|
1210
1387
|
|
|
1211
1388
|
def _init_matrix_products(self) -> None:
|
|
1212
1389
|
r"""
|
|
1213
1390
|
Initializes the global matrix products `XTX` and `XTY` for the
|
|
1214
1391
|
entire dataset. These are :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{X}`
|
|
1215
|
-
and :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{Y}`, respectively.
|
|
1392
|
+
and :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{Y}`, respectively. A product
|
|
1393
|
+
that is not computed, due to `compute_XTX` or `compute_XTY` being `False` or
|
|
1394
|
+
`Y` being `None`, is set to `None`.
|
|
1216
1395
|
"""
|
|
1217
|
-
self.XTX = self.WX.T @ self.X
|
|
1218
|
-
if self.Y is not None:
|
|
1396
|
+
self.XTX = self.WX.T @ self.X if self.compute_XTX else None
|
|
1397
|
+
if self.compute_XTY and self.Y is not None:
|
|
1219
1398
|
self.XTY = self.WX.T @ self.Y
|
|
1399
|
+
else:
|
|
1400
|
+
self.XTY = None
|
|
1220
1401
|
|
|
1221
1402
|
def _init_stats(self) -> None:
|
|
1222
1403
|
"""
|
|
1223
|
-
Initializes the global statistics for `X` and `Y`.
|
|
1404
|
+
Initializes the global statistics for `X` and `Y`. The statistics of `Y` and
|
|
1405
|
+
the squared matrices that are not computed are set to `None` so that none from
|
|
1406
|
+
a previous fit are kept.
|
|
1224
1407
|
"""
|
|
1225
1408
|
if self.center_X or self.center_Y or self.scale_X or self.scale_Y:
|
|
1226
1409
|
if self.weights is not None:
|
|
@@ -1231,15 +1414,19 @@ class CVMatrix:
|
|
|
1231
1414
|
self.num_nonzero_w = self.N
|
|
1232
1415
|
if self.center_X or self.center_Y or self.scale_X:
|
|
1233
1416
|
self.sum_X = self.xp.sum(self.WX, axis=0, keepdims=True)
|
|
1234
|
-
if
|
|
1417
|
+
if self._needs_Y_stats and self.Y is not None:
|
|
1235
1418
|
self.sum_Y = self.xp.sum(self.WY, axis=0, keepdims=True)
|
|
1419
|
+
else:
|
|
1420
|
+
self.sum_Y = None
|
|
1236
1421
|
if self.scale_X:
|
|
1237
1422
|
self.sq_X = self.WX * self.X
|
|
1238
1423
|
self.sum_sq_X = self.xp.sum(self.sq_X, axis=0, keepdims=True)
|
|
1239
1424
|
else:
|
|
1425
|
+
self.sq_X = None
|
|
1240
1426
|
self.sum_sq_X = None
|
|
1241
1427
|
if self.scale_Y and self.Y is not None:
|
|
1242
1428
|
self.sq_Y = self.WY * self.Y
|
|
1243
1429
|
self.sum_sq_Y = self.xp.sum(self.sq_Y, axis=0, keepdims=True)
|
|
1244
1430
|
else:
|
|
1431
|
+
self.sq_Y = None
|
|
1245
1432
|
self.sum_sq_Y = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cvmatrix"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.3.0"
|
|
4
4
|
description = "Fast computation of possibly weighted and possibly centered/scaled training set kernel matrices in a cross-validation setting."
|
|
5
5
|
authors = [{ name = "Sm00thix", email = "oleemail@icloud.com" }]
|
|
6
6
|
maintainers = [{ name = "Sm00thix", email = "oleemail@icloud.com" }]
|
|
@@ -14,6 +14,7 @@ classifiers = [
|
|
|
14
14
|
"Programming Language :: Python :: 3.13",
|
|
15
15
|
"Programming Language :: Python :: 3.14",
|
|
16
16
|
"Programming Language :: Python :: 3.15",
|
|
17
|
+
"Programming Language :: Python :: Free Threading :: 3 - Stable",
|
|
17
18
|
]
|
|
18
19
|
dependencies = [
|
|
19
20
|
"numpy>=2.0.0,<3.0.0",
|
|
@@ -15,8 +15,12 @@ Author: Ole-Christian Galbo Engstrøm
|
|
|
15
15
|
E-mail: ocge@foss.dk
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
import multiprocessing
|
|
19
|
+
import pickle
|
|
18
20
|
import sys
|
|
19
21
|
from collections.abc import Hashable
|
|
22
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
23
|
+
from copy import deepcopy
|
|
20
24
|
from itertools import product
|
|
21
25
|
from typing import Iterable, Union
|
|
22
26
|
|
|
@@ -682,6 +686,177 @@ class TestClass:
|
|
|
682
686
|
(XTWX, _), _ = jax.jit(cvm.training_XTX_XTY)(jnp.asarray(degenerate_val))
|
|
683
687
|
assert XTWX.shape == (4, 4)
|
|
684
688
|
|
|
689
|
+
def test_compute_matrix_products(self):
|
|
690
|
+
"""
|
|
691
|
+
Tests that `compute_XTX` and `compute_XTY` control which dataset-wide matrix
|
|
692
|
+
products `fit` computes, that the training methods needing a product that is
|
|
693
|
+
not computed raise a ValueError, and that the remaining methods give the same
|
|
694
|
+
results as when both products are computed.
|
|
695
|
+
"""
|
|
696
|
+
|
|
697
|
+
def leaves(result):
|
|
698
|
+
if isinstance(result, tuple):
|
|
699
|
+
return [leaf for r in result for leaf in leaves(r)]
|
|
700
|
+
return [result]
|
|
701
|
+
|
|
702
|
+
X = self.load_X()[:, :5]
|
|
703
|
+
Y = self.load_Y(["Protein", "Moisture"])
|
|
704
|
+
weights = self.load_weights(random=True)
|
|
705
|
+
folds = self.load_Y(["split"]).squeeze()
|
|
706
|
+
p = Partitioner(folds)
|
|
707
|
+
val_indices = p.get_validation_indices(next(iter(p.folds_dict)))
|
|
708
|
+
reference = CVMatrix(backend=self.backend)
|
|
709
|
+
reference.fit(X, Y, weights)
|
|
710
|
+
for compute_XTX, compute_XTY in product([True, False], repeat=2):
|
|
711
|
+
fast = CVMatrix(
|
|
712
|
+
backend=self.backend, compute_XTX=compute_XTX, compute_XTY=compute_XTY
|
|
713
|
+
)
|
|
714
|
+
fast.fit(X, Y, weights)
|
|
715
|
+
assert (fast.XTX is not None) == compute_XTX
|
|
716
|
+
assert (fast.XTY is not None) == compute_XTY
|
|
717
|
+
allowed = {
|
|
718
|
+
"training_XTX": compute_XTX,
|
|
719
|
+
"training_XTY": compute_XTY,
|
|
720
|
+
"training_XTX_XTY": compute_XTX and compute_XTY,
|
|
721
|
+
"training_statistics": True,
|
|
722
|
+
}
|
|
723
|
+
for method, is_allowed in allowed.items():
|
|
724
|
+
if not is_allowed:
|
|
725
|
+
with pytest.raises(ValueError, match="was not computed by `fit`"):
|
|
726
|
+
getattr(fast, method)(val_indices)
|
|
727
|
+
continue
|
|
728
|
+
actual = leaves(getattr(fast, method)(val_indices))
|
|
729
|
+
desired = leaves(getattr(reference, method)(val_indices))
|
|
730
|
+
assert len(actual) == len(desired)
|
|
731
|
+
for a, d in zip(actual, desired):
|
|
732
|
+
if d is None:
|
|
733
|
+
assert a is None
|
|
734
|
+
else:
|
|
735
|
+
assert_allclose(a, d)
|
|
736
|
+
|
|
737
|
+
# The check is on the fitted products, not the flags, so changing a flag after
|
|
738
|
+
# `fit` still raises the documented ValueError.
|
|
739
|
+
fast = CVMatrix(backend=self.backend, compute_XTX=False, compute_XTY=False)
|
|
740
|
+
fast.fit(X, Y, weights)
|
|
741
|
+
fast.compute_XTX = fast.compute_XTY = True
|
|
742
|
+
for method in ("training_XTX", "training_XTY", "training_XTX_XTY"):
|
|
743
|
+
with pytest.raises(ValueError, match="was not computed by `fit`"):
|
|
744
|
+
getattr(fast, method)(val_indices)
|
|
745
|
+
|
|
746
|
+
def test_refit_resets_Y_derived_state(self):
|
|
747
|
+
"""
|
|
748
|
+
Tests that re-fitting replaces all state derived from `Y` instead of keeping
|
|
749
|
+
it from the previous fit: re-fitting without `Y` resets `XTY`, `WY`, `sum_Y`,
|
|
750
|
+
`sq_Y`, and `sum_sq_Y` to `None`, and re-fitting with weights that do not need
|
|
751
|
+
`WY` resets `WY` to `None` instead of keeping the previous `Y`.
|
|
752
|
+
"""
|
|
753
|
+
X = self.load_X()[:, :5]
|
|
754
|
+
Y = self.load_Y(["Protein", "Moisture"])
|
|
755
|
+
weights = self.load_weights(random=True)
|
|
756
|
+
Y_derived = ("XTY", "WY", "sum_Y", "sq_Y", "sum_sq_Y")
|
|
757
|
+
fast = CVMatrix(backend=self.backend)
|
|
758
|
+
fast.fit(X, Y, weights)
|
|
759
|
+
assert all(getattr(fast, attr) is not None for attr in Y_derived)
|
|
760
|
+
fast.fit(X, None, weights)
|
|
761
|
+
assert all(getattr(fast, attr) is None for attr in Y_derived)
|
|
762
|
+
|
|
763
|
+
fast = CVMatrix(
|
|
764
|
+
center_X=False, center_Y=False, scale_X=False, scale_Y=False,
|
|
765
|
+
backend=self.backend,
|
|
766
|
+
)
|
|
767
|
+
fast.fit(X, Y) # Unweighted: `WY` is `Y` itself.
|
|
768
|
+
assert fast.WY is fast.Y
|
|
769
|
+
fast.fit(X, Y, weights) # Weighted, but no statistics of `Y` are needed.
|
|
770
|
+
assert fast.WY is None
|
|
771
|
+
|
|
772
|
+
def test_get_val_matrices_returns_only_requested(self):
|
|
773
|
+
"""
|
|
774
|
+
Tests that `_get_val_matrices` gathers only the requested validation set
|
|
775
|
+
matrices, returning `None` for the others, that the requested ones equal the
|
|
776
|
+
validation rows of `WX`, `X`, `WY`, and `Y`, and that without weights a
|
|
777
|
+
weighted and unweighted matrix requested together share a single gather.
|
|
778
|
+
"""
|
|
779
|
+
X = self.load_X()[:, :5]
|
|
780
|
+
Y = self.load_Y(["Protein", "Moisture"])
|
|
781
|
+
val_indices = np.arange(0, X.shape[0], 7)
|
|
782
|
+
for weights in (None, self.load_weights(random=True)):
|
|
783
|
+
fast = CVMatrix(backend=self.backend)
|
|
784
|
+
fast.fit(X, Y, weights)
|
|
785
|
+
w = np.ones((X.shape[0], 1)) if weights is None else weights[:, None]
|
|
786
|
+
expected = (X * w, X, Y * w, Y)
|
|
787
|
+
for flags in product([True, False], repeat=4):
|
|
788
|
+
result = fast._get_val_matrices(
|
|
789
|
+
val_indices,
|
|
790
|
+
return_X_val=flags[0],
|
|
791
|
+
return_X_val_unweighted=flags[1],
|
|
792
|
+
return_Y_val=flags[2],
|
|
793
|
+
return_Y_val_unweighted=flags[3],
|
|
794
|
+
)
|
|
795
|
+
for requested, mat_val, mat in zip(flags, result, expected):
|
|
796
|
+
if requested:
|
|
797
|
+
assert_allclose(mat_val, mat[val_indices])
|
|
798
|
+
else:
|
|
799
|
+
assert mat_val is None
|
|
800
|
+
if weights is None:
|
|
801
|
+
if flags[0] and flags[1]:
|
|
802
|
+
assert result[0] is result[1]
|
|
803
|
+
if flags[2] and flags[3]:
|
|
804
|
+
assert result[2] is result[3]
|
|
805
|
+
|
|
806
|
+
def test_pickle(self):
|
|
807
|
+
"""
|
|
808
|
+
Tests that a fitted CVMatrix survives a round trip through the standard
|
|
809
|
+
library's pickle and through copy.deepcopy, which uses the same protocol, with
|
|
810
|
+
the same settings and results, although it holds the array namespace module
|
|
811
|
+
`xp`, which cannot be pickled.
|
|
812
|
+
"""
|
|
813
|
+
X = self.load_X()[:, :5]
|
|
814
|
+
Y = self.load_Y(["Protein", "Moisture"])
|
|
815
|
+
weights = self.load_weights(random=True)
|
|
816
|
+
val_indices = np.arange(0, X.shape[0], 7)
|
|
817
|
+
fast = CVMatrix(backend=self.backend, compute_XTX=False)
|
|
818
|
+
fast.fit(X, Y, weights)
|
|
819
|
+
XTY, stats = fast.training_XTY(val_indices)
|
|
820
|
+
# Only bytes pickled by this test itself are unpickled, so Bandit's B301
|
|
821
|
+
# concern about deserializing untrusted data does not apply.
|
|
822
|
+
unpickled = pickle.loads(pickle.dumps(fast)) # nosec B301
|
|
823
|
+
for clone in (unpickled, deepcopy(fast)):
|
|
824
|
+
assert type(clone) is CVMatrix
|
|
825
|
+
assert clone.xp is fast.xp
|
|
826
|
+
assert clone.XTX is None and not clone.compute_XTX
|
|
827
|
+
clone_XTY, clone_stats = clone.training_XTY(val_indices)
|
|
828
|
+
assert_allclose(clone_XTY, XTY)
|
|
829
|
+
self.check_equivalent_stats(stats, clone_stats)
|
|
830
|
+
|
|
831
|
+
def test_process_pool(self):
|
|
832
|
+
"""
|
|
833
|
+
Tests that a fitted CVMatrix can be used from worker processes of
|
|
834
|
+
concurrent.futures.ProcessPoolExecutor, which pickles with the standard
|
|
835
|
+
library. The workers are started with `spawn`, i.e., as fresh interpreters (the
|
|
836
|
+
default on Windows and macOS). With the jax backend, this also checks that
|
|
837
|
+
float64 data stays float64 in a fresh process, where JAX's 64-bit mode is
|
|
838
|
+
initially disabled.
|
|
839
|
+
"""
|
|
840
|
+
X = self.load_X()[:, :5]
|
|
841
|
+
Y = self.load_Y(["Protein", "Moisture"])
|
|
842
|
+
weights = self.load_weights(random=True)
|
|
843
|
+
p = Partitioner(self.load_Y(["split"]).squeeze())
|
|
844
|
+
val_indices = [p.get_validation_indices(fold) for fold in p.folds_dict]
|
|
845
|
+
fast = CVMatrix(backend=self.backend)
|
|
846
|
+
fast.fit(X, Y, weights)
|
|
847
|
+
with ProcessPoolExecutor(
|
|
848
|
+
max_workers=2, mp_context=multiprocessing.get_context("spawn")
|
|
849
|
+
) as executor:
|
|
850
|
+
results = list(executor.map(fast.training_XTX_XTY, val_indices))
|
|
851
|
+
for indices, ((XTX, XTY), stats) in zip(val_indices, results):
|
|
852
|
+
(expected_XTX, expected_XTY), expected_stats = fast.training_XTX_XTY(
|
|
853
|
+
indices
|
|
854
|
+
)
|
|
855
|
+
assert np.asarray(XTX).dtype == np.asarray(XTY).dtype == np.float64
|
|
856
|
+
assert_allclose(XTX, expected_XTX, rtol=1e-12)
|
|
857
|
+
assert_allclose(XTY, expected_XTY, rtol=1e-12)
|
|
858
|
+
self.check_equivalent_stats(expected_stats, stats, atol=1e-12)
|
|
859
|
+
|
|
685
860
|
def test_naive_hadamard_vs_matmul(self):
|
|
686
861
|
"""
|
|
687
862
|
Tests if the NaiveCVMatrix model gives the same result when using Hadamard
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
version = 1
|
|
2
|
-
revision =
|
|
2
|
+
revision = 5
|
|
3
3
|
requires-python = ">=3.10, <3.16"
|
|
4
4
|
resolution-markers = [
|
|
5
5
|
"python_full_version >= '3.14' and sys_platform == 'win32'",
|
|
@@ -159,7 +159,7 @@ wheels = [
|
|
|
159
159
|
|
|
160
160
|
[package.optional-dependencies]
|
|
161
161
|
toml = [
|
|
162
|
-
{ name = "tomli"
|
|
162
|
+
{ name = "tomli" },
|
|
163
163
|
]
|
|
164
164
|
|
|
165
165
|
[[package]]
|
|
@@ -304,7 +304,7 @@ wheels = [
|
|
|
304
304
|
|
|
305
305
|
[[package]]
|
|
306
306
|
name = "cvmatrix"
|
|
307
|
-
version = "3.
|
|
307
|
+
version = "3.3.0"
|
|
308
308
|
source = { editable = "." }
|
|
309
309
|
dependencies = [
|
|
310
310
|
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
|
@@ -350,7 +350,7 @@ name = "exceptiongroup"
|
|
|
350
350
|
version = "1.3.1"
|
|
351
351
|
source = { registry = "https://pypi.org/simple" }
|
|
352
352
|
dependencies = [
|
|
353
|
-
{ name = "typing-extensions"
|
|
353
|
+
{ name = "typing-extensions" },
|
|
354
354
|
]
|
|
355
355
|
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
|
356
356
|
wheels = [
|
|
@@ -374,11 +374,11 @@ resolution-markers = [
|
|
|
374
374
|
"python_full_version < '3.11'",
|
|
375
375
|
]
|
|
376
376
|
dependencies = [
|
|
377
|
-
{ name = "jaxlib", version = "0.6.2", source = { registry = "https://pypi.org/simple" }
|
|
378
|
-
{ name = "ml-dtypes"
|
|
379
|
-
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }
|
|
380
|
-
{ name = "opt-einsum"
|
|
381
|
-
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }
|
|
377
|
+
{ name = "jaxlib", version = "0.6.2", source = { registry = "https://pypi.org/simple" } },
|
|
378
|
+
{ name = "ml-dtypes" },
|
|
379
|
+
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } },
|
|
380
|
+
{ name = "opt-einsum" },
|
|
381
|
+
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } },
|
|
382
382
|
]
|
|
383
383
|
sdist = { url = "https://files.pythonhosted.org/packages/cf/1e/267f59c8fb7f143c3f778c76cb7ef1389db3fd7e4540f04b9f42ca90764d/jax-0.6.2.tar.gz", hash = "sha256:a437d29038cbc8300334119692744704ca7941490867b9665406b7f90665cd96", size = 2334091, upload-time = "2025-06-17T23:10:27.186Z" }
|
|
384
384
|
wheels = [
|
|
@@ -395,11 +395,11 @@ resolution-markers = [
|
|
|
395
395
|
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
|
396
396
|
]
|
|
397
397
|
dependencies = [
|
|
398
|
-
{ name = "jaxlib", version = "0.10.2", source = { registry = "https://pypi.org/simple" }
|
|
399
|
-
{ name = "ml-dtypes"
|
|
400
|
-
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }
|
|
401
|
-
{ name = "opt-einsum"
|
|
402
|
-
{ name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }
|
|
398
|
+
{ name = "jaxlib", version = "0.10.2", source = { registry = "https://pypi.org/simple" } },
|
|
399
|
+
{ name = "ml-dtypes" },
|
|
400
|
+
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } },
|
|
401
|
+
{ name = "opt-einsum" },
|
|
402
|
+
{ name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" } },
|
|
403
403
|
]
|
|
404
404
|
sdist = { url = "https://files.pythonhosted.org/packages/d2/73/eb91d98fcadfa2cbcfdd4e417ab116e47eb20882acc5ee678e47c35d6b57/jax-0.10.2.tar.gz", hash = "sha256:bf77428a8c2e6904c4f46d5ab12aa5cfc6cad2179f07f7e4c0fc75ac86ef0639", size = 2775110, upload-time = "2026-06-17T23:44:57.818Z" }
|
|
405
405
|
wheels = [
|
|
@@ -422,11 +422,11 @@ resolution-markers = [
|
|
|
422
422
|
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
|
423
423
|
]
|
|
424
424
|
dependencies = [
|
|
425
|
-
{ name = "jaxlib", version = "0.12.0.1", source = { registry = "https://pypi.org/simple" }
|
|
426
|
-
{ name = "ml-dtypes"
|
|
427
|
-
{ name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" }
|
|
428
|
-
{ name = "opt-einsum"
|
|
429
|
-
{ name = "scipy", version = "1.18.1", source = { registry = "https://pypi.org/simple" }
|
|
425
|
+
{ name = "jaxlib", version = "0.12.0.1", source = { registry = "https://pypi.org/simple" } },
|
|
426
|
+
{ name = "ml-dtypes" },
|
|
427
|
+
{ name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" } },
|
|
428
|
+
{ name = "opt-einsum" },
|
|
429
|
+
{ name = "scipy", version = "1.18.1", source = { registry = "https://pypi.org/simple" } },
|
|
430
430
|
]
|
|
431
431
|
sdist = { url = "https://files.pythonhosted.org/packages/2e/5b/5b7f007852809973310ed99eede7db6e9e1cd60914f8fe90b091ae264db4/jax-0.12.0.1.tar.gz", hash = "sha256:f543fb1151c351d328b7dd9e40865c01bf9ae10cdf7e21637196172891d51914", size = 3024361, upload-time = "2026-10-21T21:07:07.969Z" }
|
|
432
432
|
wheels = [
|
|
@@ -441,9 +441,9 @@ resolution-markers = [
|
|
|
441
441
|
"python_full_version < '3.11'",
|
|
442
442
|
]
|
|
443
443
|
dependencies = [
|
|
444
|
-
{ name = "ml-dtypes"
|
|
445
|
-
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }
|
|
446
|
-
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }
|
|
444
|
+
{ name = "ml-dtypes" },
|
|
445
|
+
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } },
|
|
446
|
+
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" } },
|
|
447
447
|
]
|
|
448
448
|
wheels = [
|
|
449
449
|
{ url = "https://files.pythonhosted.org/packages/15/c5/41598634c99cbebba46e6777286fb76abc449d33d50aeae5d36128ca8803/jaxlib-0.6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4601b2b5dc8c23d6afb293eacfb9aec4e1d1871cb2f29c5a151d103e73b0f8", size = 54298019, upload-time = "2025-06-17T23:10:36.916Z" },
|
|
@@ -476,9 +476,9 @@ resolution-markers = [
|
|
|
476
476
|
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
|
477
477
|
]
|
|
478
478
|
dependencies = [
|
|
479
|
-
{ name = "ml-dtypes"
|
|
480
|
-
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }
|
|
481
|
-
{ name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }
|
|
479
|
+
{ name = "ml-dtypes" },
|
|
480
|
+
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } },
|
|
481
|
+
{ name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" } },
|
|
482
482
|
]
|
|
483
483
|
wheels = [
|
|
484
484
|
{ url = "https://files.pythonhosted.org/packages/b2/2c/7038fc73154307389631b5b2dbe5ac529e1918eecc19a27e6644ad114bbf/jaxlib-0.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5a98873fc867623b81f2bee15d554b8edd6588a183d01fa50d21b1e3db96ff2b", size = 61429039, upload-time = "2026-06-17T23:43:44.858Z" },
|
|
@@ -521,9 +521,9 @@ resolution-markers = [
|
|
|
521
521
|
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
|
522
522
|
]
|
|
523
523
|
dependencies = [
|
|
524
|
-
{ name = "ml-dtypes"
|
|
525
|
-
{ name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" }
|
|
526
|
-
{ name = "scipy", version = "1.18.1", source = { registry = "https://pypi.org/simple" }
|
|
524
|
+
{ name = "ml-dtypes" },
|
|
525
|
+
{ name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" } },
|
|
526
|
+
{ name = "scipy", version = "1.18.1", source = { registry = "https://pypi.org/simple" } },
|
|
527
527
|
]
|
|
528
528
|
wheels = [
|
|
529
529
|
{ url = "https://files.pythonhosted.org/packages/17/73/47d66eeab3bb5a48009772f4fa572e46bdf8049ecb180f83d544c933462f/jaxlib-0.12.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db2cc8584d7d0d166293ef2475b726988a10f6f9b5a8e3b8e3712415e5570380", size = 60710018, upload-time = "2026-10-21T21:05:22.443Z" },
|
|
@@ -861,10 +861,10 @@ resolution-markers = [
|
|
|
861
861
|
"python_full_version < '3.11'",
|
|
862
862
|
]
|
|
863
863
|
dependencies = [
|
|
864
|
-
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }
|
|
865
|
-
{ name = "python-dateutil"
|
|
866
|
-
{ name = "pytz"
|
|
867
|
-
{ name = "tzdata"
|
|
864
|
+
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } },
|
|
865
|
+
{ name = "python-dateutil" },
|
|
866
|
+
{ name = "pytz" },
|
|
867
|
+
{ name = "tzdata" },
|
|
868
868
|
]
|
|
869
869
|
sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" }
|
|
870
870
|
wheels = [
|
|
@@ -936,10 +936,10 @@ resolution-markers = [
|
|
|
936
936
|
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
|
937
937
|
]
|
|
938
938
|
dependencies = [
|
|
939
|
-
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version
|
|
939
|
+
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
|
|
940
940
|
{ name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
|
|
941
|
-
{ name = "python-dateutil"
|
|
942
|
-
{ name = "tzdata", marker = "
|
|
941
|
+
{ name = "python-dateutil" },
|
|
942
|
+
{ name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" },
|
|
943
943
|
]
|
|
944
944
|
sdist = { url = "https://files.pythonhosted.org/packages/55/b1/a10c0d1a7e3ce44eb42635771004aa503e3b9ddde0bd39c524dda65f7eff/pandas-3.1.0.tar.gz", hash = "sha256:ecb9b2d8a817b1e1ba1fdb90c0abe9eab31b4fdf2562b7a56b86ac66ace17e49", size = 5933421, upload-time = "2026-10-22T11:57:03.087Z" }
|
|
945
945
|
wheels = [
|
|
@@ -1108,7 +1108,7 @@ resolution-markers = [
|
|
|
1108
1108
|
"python_full_version < '3.11'",
|
|
1109
1109
|
]
|
|
1110
1110
|
dependencies = [
|
|
1111
|
-
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }
|
|
1111
|
+
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } },
|
|
1112
1112
|
]
|
|
1113
1113
|
sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" }
|
|
1114
1114
|
wheels = [
|
|
@@ -1169,7 +1169,7 @@ resolution-markers = [
|
|
|
1169
1169
|
"python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
|
1170
1170
|
]
|
|
1171
1171
|
dependencies = [
|
|
1172
|
-
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }
|
|
1172
|
+
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } },
|
|
1173
1173
|
]
|
|
1174
1174
|
sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" }
|
|
1175
1175
|
wheels = [
|
|
@@ -1251,7 +1251,7 @@ resolution-markers = [
|
|
|
1251
1251
|
"python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
|
|
1252
1252
|
]
|
|
1253
1253
|
dependencies = [
|
|
1254
|
-
{ name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" }
|
|
1254
|
+
{ name = "numpy", version = "2.5.4", source = { registry = "https://pypi.org/simple" } },
|
|
1255
1255
|
]
|
|
1256
1256
|
sdist = { url = "https://files.pythonhosted.org/packages/7e/74/66de6258867beb2ef08f35f9f2ac017a52cacd5081714d239ff1a442d458/scipy-1.18.1.tar.gz", hash = "sha256:52c4b7422442aba924d03ad4019852b08a92e64ea187b933135687bfe2747307", size = 30781235, upload-time = "2026-08-21T23:28:50.599Z" }
|
|
1257
1257
|
wheels = [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|