edmkit 0.0.15__tar.gz → 0.0.17__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.
- edmkit-0.0.17/PKG-INFO +77 -0
- edmkit-0.0.17/README.md +63 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/pyproject.toml +23 -10
- {edmkit-0.0.15 → edmkit-0.0.17}/pyproject.toml.orig +20 -10
- edmkit-0.0.17/src/edmkit/ccm.py +168 -0
- edmkit-0.0.17/src/edmkit/generate/__init__.py +40 -0
- edmkit-0.0.17/src/edmkit/generate/chua.py +31 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/generate/ctrnn.py +2 -2
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/generate/double_pendulum.py +3 -3
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/generate/duffing.py +4 -3
- edmkit-0.0.17/src/edmkit/generate/euler_spiral.py +24 -0
- edmkit-0.0.17/src/edmkit/generate/fitzhugh_nagumo.py +30 -0
- edmkit-0.0.17/src/edmkit/generate/hindmarsh_rose.py +38 -0
- edmkit-0.0.17/src/edmkit/generate/kuramoto.py +26 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/generate/lorenz.py +4 -3
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/generate/lorenz96.py +3 -3
- edmkit-0.0.17/src/edmkit/generate/lotka_volterra.py +27 -0
- edmkit-0.0.17/src/edmkit/generate/morris_lecar.py +70 -0
- edmkit-0.0.17/src/edmkit/generate/pyloric.py +105 -0
- edmkit-0.0.17/src/edmkit/generate/rk45.py +106 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/generate/rossler.py +4 -3
- edmkit-0.0.17/src/edmkit/generate/rulkov_map.py +39 -0
- edmkit-0.0.17/src/edmkit/generate/three_scroll.py +34 -0
- edmkit-0.0.17/src/edmkit/simplex_projection/knn.py +69 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/simplex_projection/linear_operator.py +20 -31
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/simplex_projection/simplex_projection.py +19 -32
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/simplex_projection/soft.py +29 -28
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/simplex_projection/soft_linear_operator.py +25 -24
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/smap/coefficients.py +35 -27
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/smap/linear_operator.py +36 -27
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/smap/smap.py +8 -7
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/theiler_window.py +15 -8
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/types.py +10 -3
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/util.py +69 -2
- edmkit-0.0.17/src/edmkit/wavelet/__init__.py +4 -0
- edmkit-0.0.17/src/edmkit/wavelet/continuous.py +276 -0
- edmkit-0.0.17/src/edmkit/wavelet/discrete.py +365 -0
- edmkit-0.0.15/PKG-INFO +0 -48
- edmkit-0.0.15/README.md +0 -34
- edmkit-0.0.15/src/edmkit/ccm.py +0 -274
- edmkit-0.0.15/src/edmkit/generate/__init__.py +0 -10
- edmkit-0.0.15/src/edmkit/generate/rk45.py +0 -85
- edmkit-0.0.15/src/edmkit/simplex_projection/knn.py +0 -33
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/embedding.py +0 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/generate/mackey_glass.py +0 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/metrics/__init__.py +0 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/metrics/mae.py +0 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/metrics/pearson_correlation.py +0 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/metrics/rmse.py +0 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/simplex_projection/__init__.py +0 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/smap/__init__.py +0 -0
- {edmkit-0.0.15 → edmkit-0.0.17}/src/edmkit/splits.py +0 -0
edmkit-0.0.17/PKG-INFO
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: edmkit
|
|
3
|
+
Version: 0.0.17
|
|
4
|
+
Summary: Simple EDM (Empirical Dynamic Modeling) library
|
|
5
|
+
Author: FUJISHIGE TEMMA
|
|
6
|
+
Author-email: FUJISHIGE TEMMA <tenma.x0@gmail.com>
|
|
7
|
+
Requires-Dist: kdtree-rs>=0.3.1
|
|
8
|
+
Requires-Dist: numpy>=2.5.4
|
|
9
|
+
Requires-Dist: scipy>=1.18.1
|
|
10
|
+
Requires-Dist: tinygrad>=0.14.0
|
|
11
|
+
Requires-Dist: tinysolve>=0.3.0
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# edmkit
|
|
16
|
+
|
|
17
|
+
This library is a collection of tools and utilities that are useful for Empirical Data Modeling (EDM) and related tasks. The library is designed to be fast and lightweight, and easy to use.
|
|
18
|
+
|
|
19
|
+
[benchmarks](./benchmarks)
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
[compare.py](./benchmarks/compare.py)
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
| simplex N | edmkit [ms] | pyEDM [ms] | speedup |
|
|
28
|
+
|---:|---:|---:|---:|
|
|
29
|
+
| 500 | 0.2 | 1.4 | 8x |
|
|
30
|
+
| 1,000 | 0.4 | 2.3 | 5x |
|
|
31
|
+
| 2,000 | 0.9 | 4.2 | 5x |
|
|
32
|
+
| 5,000 | 2.3 | 10.7 | 5x |
|
|
33
|
+
| 10,000 | 4.9 | 22.8 | 5x |
|
|
34
|
+
| 20,000 | 10.3 | 46.8 | 5x |
|
|
35
|
+
|
|
36
|
+
| smap N | edmkit [ms] | pyEDM [ms] | speedup |
|
|
37
|
+
|---:|---:|---:|---:|
|
|
38
|
+
| 500 | 1.9 | 38.0 | 20x |
|
|
39
|
+
| 1,000 | 7.5 | 132.1 | 18x |
|
|
40
|
+
| 2,000 | 21.6 | 522.6 | 24x |
|
|
41
|
+
| 5,000 | 183.7 | 3,364 | 18x |
|
|
42
|
+
|
|
43
|
+
| ccm N | edmkit [ms] | pyEDM [ms] | speedup | edmkit parallel [ms] | pyEDM parallel [ms] | speedup |
|
|
44
|
+
|---:|---:|---:|---:|---:|---:|---:|
|
|
45
|
+
| 500 | 116.7 | 148.4 | 1.3x | 22.0 | 64.5 | 2.9x |
|
|
46
|
+
| 1,000 | 197.1 | 316.8 | 1.6x | 31.8 | 89.7 | 2.8x |
|
|
47
|
+
| 2,000 | 382.8 | 696.8 | 1.8x | 58.6 | 133.8 | 2.3x |
|
|
48
|
+
| 5,000 | 788.0 | 1,863 | 2.4x | 108.9 | 283.5 | 2.6x |
|
|
49
|
+
| 10,000 | 1,470 | 4,166 | 2.8x | 197.0 | 558.9 | 2.8x |
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
To install the library, you can use pip:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install edmkit
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Or you can also use [uv](https://docs.astral.sh/uv/):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv add edmkit
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Testing
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Run all tests
|
|
70
|
+
uv run pytest
|
|
71
|
+
|
|
72
|
+
# Skip slow tests
|
|
73
|
+
uv run pytest -m "not slow"
|
|
74
|
+
|
|
75
|
+
# Run with CI profile (more hypothesis examples)
|
|
76
|
+
HYPOTHESIS_PROFILE=ci uv run pytest
|
|
77
|
+
```
|
edmkit-0.0.17/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# edmkit
|
|
2
|
+
|
|
3
|
+
This library is a collection of tools and utilities that are useful for Empirical Data Modeling (EDM) and related tasks. The library is designed to be fast and lightweight, and easy to use.
|
|
4
|
+
|
|
5
|
+
[benchmarks](./benchmarks)
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
[compare.py](./benchmarks/compare.py)
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
| simplex N | edmkit [ms] | pyEDM [ms] | speedup |
|
|
14
|
+
|---:|---:|---:|---:|
|
|
15
|
+
| 500 | 0.2 | 1.4 | 8x |
|
|
16
|
+
| 1,000 | 0.4 | 2.3 | 5x |
|
|
17
|
+
| 2,000 | 0.9 | 4.2 | 5x |
|
|
18
|
+
| 5,000 | 2.3 | 10.7 | 5x |
|
|
19
|
+
| 10,000 | 4.9 | 22.8 | 5x |
|
|
20
|
+
| 20,000 | 10.3 | 46.8 | 5x |
|
|
21
|
+
|
|
22
|
+
| smap N | edmkit [ms] | pyEDM [ms] | speedup |
|
|
23
|
+
|---:|---:|---:|---:|
|
|
24
|
+
| 500 | 1.9 | 38.0 | 20x |
|
|
25
|
+
| 1,000 | 7.5 | 132.1 | 18x |
|
|
26
|
+
| 2,000 | 21.6 | 522.6 | 24x |
|
|
27
|
+
| 5,000 | 183.7 | 3,364 | 18x |
|
|
28
|
+
|
|
29
|
+
| ccm N | edmkit [ms] | pyEDM [ms] | speedup | edmkit parallel [ms] | pyEDM parallel [ms] | speedup |
|
|
30
|
+
|---:|---:|---:|---:|---:|---:|---:|
|
|
31
|
+
| 500 | 116.7 | 148.4 | 1.3x | 22.0 | 64.5 | 2.9x |
|
|
32
|
+
| 1,000 | 197.1 | 316.8 | 1.6x | 31.8 | 89.7 | 2.8x |
|
|
33
|
+
| 2,000 | 382.8 | 696.8 | 1.8x | 58.6 | 133.8 | 2.3x |
|
|
34
|
+
| 5,000 | 788.0 | 1,863 | 2.4x | 108.9 | 283.5 | 2.6x |
|
|
35
|
+
| 10,000 | 1,470 | 4,166 | 2.8x | 197.0 | 558.9 | 2.8x |
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
To install the library, you can use pip:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install edmkit
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or you can also use [uv](https://docs.astral.sh/uv/):
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv add edmkit
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Testing
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Run all tests
|
|
56
|
+
uv run pytest
|
|
57
|
+
|
|
58
|
+
# Skip slow tests
|
|
59
|
+
uv run pytest -m "not slow"
|
|
60
|
+
|
|
61
|
+
# Run with CI profile (more hypothesis examples)
|
|
62
|
+
HYPOTHESIS_PROFILE=ci uv run pytest
|
|
63
|
+
```
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "edmkit"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.17"
|
|
4
4
|
description = "Simple EDM (Empirical Dynamic Modeling) library"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">= 3.13"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"kdtree-rs>=0.3.1",
|
|
9
|
-
"numpy>=2.5.
|
|
10
|
-
"scipy>=1.
|
|
9
|
+
"numpy>=2.5.4",
|
|
10
|
+
"scipy>=1.18.1",
|
|
11
11
|
"tinygrad>=0.14.0",
|
|
12
|
-
"tinysolve>=0.
|
|
12
|
+
"tinysolve>=0.3.0",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
[[project.authors]]
|
|
@@ -17,13 +17,19 @@ name = "FUJISHIGE TEMMA"
|
|
|
17
17
|
email = "tenma.x0@gmail.com"
|
|
18
18
|
|
|
19
19
|
[dependency-groups]
|
|
20
|
+
benchmark = [
|
|
21
|
+
"pandas>=3.1.0",
|
|
22
|
+
"pyedm>=2.5.7",
|
|
23
|
+
]
|
|
20
24
|
dev = [
|
|
21
|
-
"griffe2md>=1.5.
|
|
22
|
-
"hypothesis>=6.
|
|
25
|
+
"griffe2md>=1.5.1",
|
|
26
|
+
"hypothesis>=6.170.14",
|
|
23
27
|
"matplotlib>=3.11.2",
|
|
24
|
-
"pytest>=9.
|
|
25
|
-
"
|
|
26
|
-
"
|
|
28
|
+
"pytest>=9.2.0",
|
|
29
|
+
"pytest-benchmark>=5.3.1",
|
|
30
|
+
"pywavelets>=1.10.0",
|
|
31
|
+
"ruff>=0.17.3",
|
|
32
|
+
"ty>=0.0.95",
|
|
27
33
|
]
|
|
28
34
|
|
|
29
35
|
[tool.uv]
|
|
@@ -35,12 +41,19 @@ namespace = true
|
|
|
35
41
|
|
|
36
42
|
[tool.pytest.ini_options]
|
|
37
43
|
addopts = "-v --tb=short"
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
python_files = [
|
|
46
|
+
"test_*.py",
|
|
47
|
+
"bench_*.py",
|
|
48
|
+
]
|
|
38
49
|
filterwarnings = ["error"]
|
|
39
50
|
markers = [
|
|
40
51
|
"""slow: marks tests as slow (deselect with '-m "not slow"')""",
|
|
41
52
|
"""gpu: marks tests requiring tinygrad tensor backend (deselect with '-m "not gpu"')""",
|
|
53
|
+
"dense: marks benchmarks that build an (M, N) matrix",
|
|
54
|
+
"""huge: marks benchmarks whose N is too large for an (M, N) matrix in memory (deselect with '-m "not (dense and huge)"')""",
|
|
42
55
|
]
|
|
43
56
|
|
|
44
57
|
[build-system]
|
|
45
|
-
requires = ["uv_build>=0.
|
|
58
|
+
requires = ["uv_build>=0.13.6,<0.14.0"]
|
|
46
59
|
build-backend = "uv_build"
|
|
@@ -1,26 +1,32 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "edmkit"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.17"
|
|
4
4
|
description = "Simple EDM (Empirical Dynamic Modeling) library"
|
|
5
5
|
authors = [{ name = "FUJISHIGE TEMMA", email = "tenma.x0@gmail.com" }]
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">= 3.13"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"kdtree-rs>=0.3.1",
|
|
10
|
-
"numpy>=2.5.
|
|
11
|
-
"scipy>=1.
|
|
10
|
+
"numpy>=2.5.4",
|
|
11
|
+
"scipy>=1.18.1",
|
|
12
12
|
"tinygrad>=0.14.0",
|
|
13
|
-
"tinysolve>=0.
|
|
13
|
+
"tinysolve>=0.3.0",
|
|
14
14
|
]
|
|
15
15
|
|
|
16
16
|
[dependency-groups]
|
|
17
|
+
benchmark = [
|
|
18
|
+
"pandas>=3.1.0",
|
|
19
|
+
"pyedm>=2.5.7",
|
|
20
|
+
]
|
|
17
21
|
dev = [
|
|
18
|
-
"griffe2md>=1.5.
|
|
19
|
-
"hypothesis>=6.
|
|
22
|
+
"griffe2md>=1.5.1",
|
|
23
|
+
"hypothesis>=6.170.14",
|
|
20
24
|
"matplotlib>=3.11.2",
|
|
21
|
-
"pytest>=9.
|
|
22
|
-
"
|
|
23
|
-
"
|
|
25
|
+
"pytest>=9.2.0",
|
|
26
|
+
"pytest-benchmark>=5.3.1",
|
|
27
|
+
"pywavelets>=1.10.0",
|
|
28
|
+
"ruff>=0.17.3",
|
|
29
|
+
"ty>=0.0.95",
|
|
24
30
|
]
|
|
25
31
|
|
|
26
32
|
[tool.uv]
|
|
@@ -30,15 +36,19 @@ required-environments = [
|
|
|
30
36
|
|
|
31
37
|
[tool.pytest.ini_options]
|
|
32
38
|
addopts = "-v --tb=short"
|
|
39
|
+
testpaths = ["tests"]
|
|
40
|
+
python_files = ["test_*.py", "bench_*.py"]
|
|
33
41
|
filterwarnings = ["error"]
|
|
34
42
|
markers = [
|
|
35
43
|
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
36
44
|
"gpu: marks tests requiring tinygrad tensor backend (deselect with '-m \"not gpu\"')",
|
|
45
|
+
"dense: marks benchmarks that build an (M, N) matrix",
|
|
46
|
+
"huge: marks benchmarks whose N is too large for an (M, N) matrix in memory (deselect with '-m \"not (dense and huge)\"')",
|
|
37
47
|
]
|
|
38
48
|
|
|
39
49
|
|
|
40
50
|
[build-system]
|
|
41
|
-
requires = ["uv_build>=0.
|
|
51
|
+
requires = ["uv_build>=0.13.6,<0.14.0"]
|
|
42
52
|
build-backend = "uv_build"
|
|
43
53
|
|
|
44
54
|
[tool.uv.build-backend]
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from collections.abc import Callable, Iterator
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from edmkit.types import Mask, PredictFunc
|
|
7
|
+
|
|
8
|
+
__all__ = ["bootstrap"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def bootstrap(
|
|
12
|
+
X: np.ndarray,
|
|
13
|
+
Y: np.ndarray,
|
|
14
|
+
Q: np.ndarray,
|
|
15
|
+
lib_sizes: np.ndarray,
|
|
16
|
+
*,
|
|
17
|
+
predict_func: PredictFunc,
|
|
18
|
+
n_samples: int = 20,
|
|
19
|
+
replace: bool = True,
|
|
20
|
+
seed: int | None = 42,
|
|
21
|
+
mask: Mask | None = None,
|
|
22
|
+
batch_size: int | None = 20,
|
|
23
|
+
) -> Iterator[tuple[tuple[int, slice], Callable[[], np.ndarray]]]:
|
|
24
|
+
"""
|
|
25
|
+
Iterate `predict_func(X[library], Y[library], Q)` for `n_samples` times for each library size in `lib_sizes`.
|
|
26
|
+
|
|
27
|
+
By gathering the results of the returned tasks, you can get `(len(lib_sizes), n_samples, M, E')` array of predictions.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
X : np.ndarray
|
|
32
|
+
The input data of shape (N,) or (N, E)
|
|
33
|
+
Y : np.ndarray
|
|
34
|
+
The target data of shape (N,) or (N, E'), sampled together with `X`.
|
|
35
|
+
Q : np.ndarray
|
|
36
|
+
The query points of shape (M,) or (M, E), shared by all sampled libraries as a read-only view.
|
|
37
|
+
lib_sizes : np.ndarray
|
|
38
|
+
The 1D array of library sizes, yielded in the given order. Sizes may exceed N only if `replace` is True.
|
|
39
|
+
predict_func : PredictFunc
|
|
40
|
+
The batched prediction function, such as `simplex_projection` or ``functools.partial(smap, theta=...)``.
|
|
41
|
+
n_samples : int, default 20
|
|
42
|
+
The number of libraries to sample for each size in `lib_sizes`.
|
|
43
|
+
replace : bool, default True
|
|
44
|
+
Whether to sample the library points with replacement.
|
|
45
|
+
seed : int or None, default 42
|
|
46
|
+
The seed of the random generator. If None, uses fresh entropy.
|
|
47
|
+
mask : Mask or None
|
|
48
|
+
``mask(i, j)`` indicating, for each query in `Q`, which points of `X` to include. Use `theiler_window` for temporal exclusion.
|
|
49
|
+
batch_size : int or None, default 20
|
|
50
|
+
The maximum number of libraries per task. If None, uses `n_samples`.
|
|
51
|
+
|
|
52
|
+
Yields
|
|
53
|
+
------
|
|
54
|
+
index : tuple[int, slice]
|
|
55
|
+
The position of the task's libraries in an array of shape (len(`lib_sizes`), `n_samples`), such as the scores.
|
|
56
|
+
samples : Callable[[], np.ndarray]
|
|
57
|
+
The task that returns the predictions on B sampled libraries, of shape (B, M, E'), where B <= `batch_size` and E' = 1 if `Y` is 1D.
|
|
58
|
+
|
|
59
|
+
Raises
|
|
60
|
+
------
|
|
61
|
+
ValueError
|
|
62
|
+
- If `X`, `Y`, or `Q` is empty or not 1D or 2D.
|
|
63
|
+
- If the input arrays `X` and `Y` do not have the same number of points.
|
|
64
|
+
- If the dimension of `X` and `Q` do not match.
|
|
65
|
+
- If `lib_sizes` is not a nonempty 1D array of positive integers.
|
|
66
|
+
- If some size in `lib_sizes` exceeds N when `replace` is False.
|
|
67
|
+
- If `n_samples` or `batch_size` is not positive.
|
|
68
|
+
- If `predict_func` raises when a task is executed.
|
|
69
|
+
|
|
70
|
+
Examples
|
|
71
|
+
--------
|
|
72
|
+
```python
|
|
73
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
74
|
+
|
|
75
|
+
import numpy as np
|
|
76
|
+
|
|
77
|
+
from edmkit.ccm import bootstrap
|
|
78
|
+
from edmkit.embedding import embed
|
|
79
|
+
from edmkit.metrics import pearson_correlation
|
|
80
|
+
from edmkit.simplex_projection import simplex_projection
|
|
81
|
+
|
|
82
|
+
# Generate a simple time series (logistic map)
|
|
83
|
+
N = 300
|
|
84
|
+
x = np.zeros(N)
|
|
85
|
+
x[0] = 0.4
|
|
86
|
+
for i in range(1, N):
|
|
87
|
+
x[i] = 3.9 * x[i - 1] * (1 - x[i - 1])
|
|
88
|
+
|
|
89
|
+
tau = 2
|
|
90
|
+
E = 3
|
|
91
|
+
|
|
92
|
+
coordinates = np.array([[0, tau * j] for j in range(E)])
|
|
93
|
+
embedding, _ = embed(coordinates, x)
|
|
94
|
+
shift = tau * (E - 1)
|
|
95
|
+
|
|
96
|
+
pool_size = 200
|
|
97
|
+
Tp = 1
|
|
98
|
+
X = embedding[:pool_size - shift]
|
|
99
|
+
Y = x[shift + Tp : pool_size + Tp]
|
|
100
|
+
Q = embedding[pool_size - shift : -Tp]
|
|
101
|
+
actual = x[pool_size + Tp :, None]
|
|
102
|
+
|
|
103
|
+
lib_sizes = np.array([10, 50, 190])
|
|
104
|
+
n_samples = 100
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def score(samples):
|
|
108
|
+
predictions = samples() # (B, M, 1)
|
|
109
|
+
return pearson_correlation(predictions, np.broadcast_to(actual, predictions.shape))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
scores = np.empty((len(lib_sizes), n_samples))
|
|
113
|
+
tasks = bootstrap(X, Y, Q, lib_sizes, predict_func=simplex_projection, n_samples=n_samples)
|
|
114
|
+
indices, samples = zip(*tasks)
|
|
115
|
+
with ThreadPoolExecutor() as executor:
|
|
116
|
+
for index, result in zip(indices, executor.map(score, samples)):
|
|
117
|
+
scores[index] = result
|
|
118
|
+
|
|
119
|
+
for lib_size, correlation in zip(lib_sizes, scores.mean(axis=1)):
|
|
120
|
+
print(f"Correlation (L={lib_size}): {correlation:.3f}")
|
|
121
|
+
```
|
|
122
|
+
"""
|
|
123
|
+
if X.ndim == 1:
|
|
124
|
+
X = X[:, None]
|
|
125
|
+
if Y.ndim == 1:
|
|
126
|
+
Y = Y[:, None]
|
|
127
|
+
if Q.ndim == 1:
|
|
128
|
+
Q = Q[:, None]
|
|
129
|
+
|
|
130
|
+
if X.ndim != 2 or Y.ndim != 2 or Q.ndim != 2:
|
|
131
|
+
raise ValueError(f"X, Y, and Q must be 1D or 2D arrays, got X.ndim={X.ndim}, Y.ndim={Y.ndim}, Q.ndim={Q.ndim}")
|
|
132
|
+
if X.size == 0 or Y.size == 0 or Q.size == 0:
|
|
133
|
+
raise ValueError("X, Y, and Q must be nonempty")
|
|
134
|
+
if X.shape[0] != Y.shape[0]:
|
|
135
|
+
raise ValueError(f"X and Y must have the same length, got X.shape={X.shape}, Y.shape={Y.shape}")
|
|
136
|
+
if X.shape[1] != Q.shape[1]:
|
|
137
|
+
raise ValueError(f"X and Q must have the same dimension, got X.shape={X.shape}, Q.shape={Q.shape}")
|
|
138
|
+
if lib_sizes.ndim != 1 or lib_sizes.size == 0 or not np.issubdtype(lib_sizes.dtype, np.integer) or np.any(lib_sizes <= 0):
|
|
139
|
+
raise ValueError("lib_sizes must be a nonempty 1D integer array of positive sizes")
|
|
140
|
+
if not replace and np.any(lib_sizes > len(X)):
|
|
141
|
+
raise ValueError(f"lib_sizes cannot exceed N={len(X)} when replace is False")
|
|
142
|
+
batch_size = n_samples if batch_size is None else batch_size
|
|
143
|
+
if n_samples <= 0 or batch_size <= 0:
|
|
144
|
+
raise ValueError(f"n_samples and batch_size must be positive, got n_samples={n_samples}, batch_size={batch_size}")
|
|
145
|
+
|
|
146
|
+
rng = np.random.default_rng(seed)
|
|
147
|
+
for i, lib_size in enumerate(lib_sizes):
|
|
148
|
+
for start in range(0, n_samples, batch_size):
|
|
149
|
+
batch = min(batch_size, n_samples - start)
|
|
150
|
+
if replace:
|
|
151
|
+
indices = rng.choice(len(X), size=(batch, lib_size))
|
|
152
|
+
else:
|
|
153
|
+
indices = np.stack([rng.choice(len(X), size=lib_size, replace=False) for _ in range(batch)])
|
|
154
|
+
yield (i, slice(start, start + batch)), partial(samples, X, Y, Q, indices, predict_func=predict_func, mask=mask)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def samples(
|
|
158
|
+
X: np.ndarray,
|
|
159
|
+
Y: np.ndarray,
|
|
160
|
+
Q: np.ndarray,
|
|
161
|
+
indices: np.ndarray,
|
|
162
|
+
*,
|
|
163
|
+
predict_func: PredictFunc,
|
|
164
|
+
mask: Mask | None,
|
|
165
|
+
) -> np.ndarray:
|
|
166
|
+
queries = np.broadcast_to(Q, (len(indices), *Q.shape))
|
|
167
|
+
# position j of library b is the point indices[b, j] of X
|
|
168
|
+
return predict_func(X[indices], Y[indices], queries, mask=None if mask is None else (lambda b, i, j: mask(i, indices[b, j])))
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from .chua import chua
|
|
2
|
+
from .ctrnn import ctrnn
|
|
3
|
+
from .double_pendulum import double_pendulum, to_xy
|
|
4
|
+
from .duffing import duffing
|
|
5
|
+
from .euler_spiral import euler_spiral
|
|
6
|
+
from .fitzhugh_nagumo import fitzhugh_nagumo
|
|
7
|
+
from .hindmarsh_rose import hindmarsh_rose
|
|
8
|
+
from .kuramoto import kuramoto
|
|
9
|
+
from .lorenz import lorenz
|
|
10
|
+
from .lorenz96 import lorenz96
|
|
11
|
+
from .lotka_volterra import lotka_volterra
|
|
12
|
+
from .mackey_glass import mackey_glass
|
|
13
|
+
from .morris_lecar import morris_lecar
|
|
14
|
+
from .pyloric import pyloric
|
|
15
|
+
from .rk45 import rk45
|
|
16
|
+
from .rossler import rossler
|
|
17
|
+
from .rulkov_map import rulkov_map
|
|
18
|
+
from .three_scroll import three_scroll
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"chua",
|
|
22
|
+
"ctrnn",
|
|
23
|
+
"double_pendulum",
|
|
24
|
+
"duffing",
|
|
25
|
+
"euler_spiral",
|
|
26
|
+
"fitzhugh_nagumo",
|
|
27
|
+
"hindmarsh_rose",
|
|
28
|
+
"kuramoto",
|
|
29
|
+
"lorenz",
|
|
30
|
+
"lorenz96",
|
|
31
|
+
"lotka_volterra",
|
|
32
|
+
"mackey_glass",
|
|
33
|
+
"morris_lecar",
|
|
34
|
+
"pyloric",
|
|
35
|
+
"rk45",
|
|
36
|
+
"rossler",
|
|
37
|
+
"rulkov_map",
|
|
38
|
+
"three_scroll",
|
|
39
|
+
"to_xy",
|
|
40
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
__all__ = ["chua"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def chua(alpha: float, beta: float, m0: float, m1: float):
|
|
7
|
+
"""Vector field of Chua's circuit, with the piecewise-linear diode ``h(x) = m1 x + (m0 - m1) (|x + 1| - |x - 1|) / 2``.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
alpha : float
|
|
12
|
+
Ratio of capacitances (typical: 15.6).
|
|
13
|
+
beta : float
|
|
14
|
+
Ratio of inductance to resistance (typical: 28).
|
|
15
|
+
m0 : float
|
|
16
|
+
Diode slope for ``|x| < 1`` (typical: -8/7).
|
|
17
|
+
m1 : float
|
|
18
|
+
Diode slope for ``|x| > 1`` (typical: -5/7).
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
f : Callable[[float, np.ndarray], np.ndarray]
|
|
23
|
+
Right-hand side ``f(t, x)`` for a state ``(x, y, z)`` of shape ``(3,)``, or ``(B, 3)`` for a batch.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def f(t: float, s: np.ndarray):
|
|
27
|
+
x, y, z = np.moveaxis(s, -1, 0)
|
|
28
|
+
h = m1 * x + (m0 - m1) * np.clip(x, -1, 1) # (|x + 1| - |x - 1|) / 2 is clip(x, -1, 1)
|
|
29
|
+
return np.stack([alpha * (y - x - h), x - y + z, -beta * y], axis=-1)
|
|
30
|
+
|
|
31
|
+
return f
|
|
@@ -16,10 +16,10 @@ def ctrnn(g: float, J: np.ndarray):
|
|
|
16
16
|
Returns
|
|
17
17
|
-------
|
|
18
18
|
f : Callable[[float, np.ndarray], np.ndarray]
|
|
19
|
-
Right-hand side ``f(t, x)`` for a state of shape ``(N,)
|
|
19
|
+
Right-hand side ``f(t, x)`` for a state of shape ``(N,)``, or ``(B, N)`` for a batch.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
def f(t: float, x: np.ndarray):
|
|
23
|
-
return -x + g *
|
|
23
|
+
return -x + g * np.tanh(x) @ J.T
|
|
24
24
|
|
|
25
25
|
return f
|
|
@@ -22,11 +22,11 @@ def double_pendulum(m1: float, m2: float, L1: float, L2: float, g: float):
|
|
|
22
22
|
Returns
|
|
23
23
|
-------
|
|
24
24
|
f : Callable[[float, np.ndarray], np.ndarray]
|
|
25
|
-
Right-hand side ``f(t, x)`` for a state ``(theta1, theta2, omega1, omega2)`` of shape ``(4,)
|
|
25
|
+
Right-hand side ``f(t, x)`` for a state ``(theta1, theta2, omega1, omega2)`` of shape ``(4,)``, or ``(B, 4)`` for a batch.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
28
|
def f(t: float, x: np.ndarray):
|
|
29
|
-
theta1, theta2, omega1, omega2 = x
|
|
29
|
+
theta1, theta2, omega1, omega2 = np.moveaxis(x, -1, 0)
|
|
30
30
|
delta = theta1 - theta2
|
|
31
31
|
|
|
32
32
|
denom = 2 * m1 + m2 - m2 * np.cos(2 * delta)
|
|
@@ -44,7 +44,7 @@ def double_pendulum(m1: float, m2: float, L1: float, L2: float, g: float):
|
|
|
44
44
|
L2 * denom
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
-
return np.
|
|
47
|
+
return np.stack([dtheta1_dt, dtheta2_dt, domega1_dt, domega2_dt], axis=-1)
|
|
48
48
|
|
|
49
49
|
return f
|
|
50
50
|
|
|
@@ -22,10 +22,11 @@ def duffing(alpha: float, beta: float, delta: float, gamma: float, omega: float)
|
|
|
22
22
|
Returns
|
|
23
23
|
-------
|
|
24
24
|
f : Callable[[float, np.ndarray], np.ndarray]
|
|
25
|
-
Right-hand side ``f(t, x)`` for a state ``(x, v)`` of shape ``(2,)
|
|
25
|
+
Right-hand side ``f(t, x)`` for a state ``(x, v)`` of shape ``(2,)``, or ``(B, 2)`` for a batch.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
def f(t: float,
|
|
29
|
-
|
|
28
|
+
def f(t: float, s: np.ndarray):
|
|
29
|
+
x, v = np.moveaxis(s, -1, 0)
|
|
30
|
+
return np.stack([v, (-alpha - beta * x**2) * x - delta * v + gamma * np.cos(omega * t)], axis=-1)
|
|
30
31
|
|
|
31
32
|
return f
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
__all__ = ["euler_spiral"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def euler_spiral(a: float):
|
|
7
|
+
"""Vector field of the Euler spiral (clothoid).
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
a : float
|
|
12
|
+
Rate at which the curvature grows (typical: 1).
|
|
13
|
+
|
|
14
|
+
Returns
|
|
15
|
+
-------
|
|
16
|
+
f : Callable[[float, np.ndarray], np.ndarray]
|
|
17
|
+
Right-hand side ``f(t, x)`` for a state ``(x, y, theta)`` of shape ``(3,)``, or ``(B, 3)`` for a batch.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def f(t: float, x: np.ndarray):
|
|
21
|
+
theta = x[..., 2]
|
|
22
|
+
return np.stack([np.cos(theta), np.sin(theta), np.full_like(theta, a * t)], axis=-1)
|
|
23
|
+
|
|
24
|
+
return f
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
__all__ = ["fitzhugh_nagumo"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def fitzhugh_nagumo(a: float, b: float, epsilon: float, I: float):
|
|
7
|
+
"""Vector field of the FitzHugh-Nagumo neuron model.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
a : float
|
|
12
|
+
Offset of the recovery variable (typical: 0.7).
|
|
13
|
+
b : float
|
|
14
|
+
Damping of the recovery variable (typical: 0.8).
|
|
15
|
+
epsilon : float
|
|
16
|
+
Time scale of the recovery variable (typical: 0.08).
|
|
17
|
+
I : float
|
|
18
|
+
Applied current (typical: 0.5 for spiking).
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
f : Callable[[float, np.ndarray], np.ndarray]
|
|
23
|
+
Right-hand side ``f(t, x)`` for a state ``(v, w)`` of shape ``(2,)``, or ``(B, 2)`` for a batch.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def f(t: float, x: np.ndarray):
|
|
27
|
+
v, w = np.moveaxis(x, -1, 0)
|
|
28
|
+
return np.stack([v - v**3 / 3 - w + I, epsilon * (v + a - b * w)], axis=-1)
|
|
29
|
+
|
|
30
|
+
return f
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
__all__ = ["hindmarsh_rose"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def hindmarsh_rose(a: float, b: float, c: float, d: float, r: float, s: float, x_R: float, I: float):
|
|
7
|
+
"""Vector field of the Hindmarsh-Rose neuron model.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
a : float
|
|
12
|
+
Cubic coefficient of the membrane potential (typical: 1).
|
|
13
|
+
b : float
|
|
14
|
+
Quadratic coefficient of the membrane potential (typical: 3).
|
|
15
|
+
c : float
|
|
16
|
+
Constant drive of the recovery variable (typical: 1).
|
|
17
|
+
d : float
|
|
18
|
+
Quadratic coupling of the potential into the recovery variable (typical: 5).
|
|
19
|
+
r : float
|
|
20
|
+
Time scale of the slow adaptation current (typical: 0.006).
|
|
21
|
+
s : float
|
|
22
|
+
Adaptation strength (typical: 4).
|
|
23
|
+
x_R : float
|
|
24
|
+
Resting potential (typical: -1.6).
|
|
25
|
+
I : float
|
|
26
|
+
Applied current (typical: 3.25 for chaotic bursting).
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
f : Callable[[float, np.ndarray], np.ndarray]
|
|
31
|
+
Right-hand side ``f(t, x)`` for a state ``(x, y, z)`` of shape ``(3,)``, or ``(B, 3)`` for a batch: membrane potential, recovery variable and adaptation current.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def f(t: float, x: np.ndarray):
|
|
35
|
+
v, w, u = np.moveaxis(x, -1, 0)
|
|
36
|
+
return np.stack([w - a * v**3 + b * v**2 - u + I, c - d * v**2 - w, r * (s * (v - x_R) - u)], axis=-1)
|
|
37
|
+
|
|
38
|
+
return f
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
__all__ = ["kuramoto"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def kuramoto(omega: np.ndarray, K: np.ndarray):
|
|
7
|
+
"""Vector field of the Kuramoto model ``dθ_i/dt = ω_i + Σ_j K_ij sin(θ_j - θ_i)``.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
omega : np.ndarray
|
|
12
|
+
Natural frequencies of shape ``(N,)``.
|
|
13
|
+
K : np.ndarray
|
|
14
|
+
Coupling of shape ``(N, N)``; ``K[i, j]`` is the strength with which oscillator ``j`` drives oscillator ``i`` (typical: ``k / N`` everywhere).
|
|
15
|
+
|
|
16
|
+
Returns
|
|
17
|
+
-------
|
|
18
|
+
f : Callable[[float, np.ndarray], np.ndarray]
|
|
19
|
+
Right-hand side ``f(t, x)`` for unwrapped phases of shape ``(N,)``, or ``(B, N)`` for a batch.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def f(t: float, x: np.ndarray):
|
|
23
|
+
s, c = np.sin(x), np.cos(x)
|
|
24
|
+
return omega + c * (s @ K.T) - s * (c @ K.T)
|
|
25
|
+
|
|
26
|
+
return f
|