epgx 0.1.dev0__py3-none-any.whl
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.
- epgx/__init__.py +19 -0
- epgx/axes.py +285 -0
- epgx/common.py +63 -0
- epgx/compiler.py +347 -0
- epgx/diff.py +242 -0
- epgx/examples.py +146 -0
- epgx/functions.py +602 -0
- epgx/kernels/__init__.py +9 -0
- epgx/kernels/affine.py +187 -0
- epgx/kernels/evolution.py +40 -0
- epgx/kernels/operator.py +32 -0
- epgx/kernels/probe.py +107 -0
- epgx/kernels/shift.py +106 -0
- epgx/kernels/state.py +447 -0
- epgx/kernels/transition.py +48 -0
- epgx/library/__init__.py +3 -0
- epgx/library/sequences.py +210 -0
- epgx/lowering.py +95 -0
- epgx/merging.py +137 -0
- epgx/methods.py +329 -0
- epgx/ops/__init__.py +30 -0
- epgx/ops/dephasing.py +71 -0
- epgx/ops/evolution.py +135 -0
- epgx/ops/functions.py +158 -0
- epgx/ops/operator.py +224 -0
- epgx/ops/probe.py +116 -0
- epgx/ops/shift.py +118 -0
- epgx/ops/transition.py +58 -0
- epgx/plotting.py +226 -0
- epgx/progress.py +81 -0
- epgx/sample.py +142 -0
- epgx/sequences.py +850 -0
- epgx/stats.py +117 -0
- epgx-0.1.dev0.dist-info/METADATA +64 -0
- epgx-0.1.dev0.dist-info/RECORD +38 -0
- epgx-0.1.dev0.dist-info/WHEEL +5 -0
- epgx-0.1.dev0.dist-info/licenses/LICENSE +28 -0
- epgx-0.1.dev0.dist-info/top_level.txt +1 -0
epgx/stats.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Cramer-Rao lower bound and confidence intervals
|
|
2
|
+
|
|
3
|
+
Differentiable (jnp): the gradient of the CRLB w.r.t. sequence parameters is a
|
|
4
|
+
`jax.grad` (no separate gradient function).
|
|
5
|
+
|
|
6
|
+
Layout (that of `simulate` and `diff.derivatives`):
|
|
7
|
+
J: (nobs, *batch, nparam) Jacobian, observations first
|
|
8
|
+
H: (nobs, *batch, nparam, nparam) Hessian
|
|
9
|
+
obs, pred: (nobs, *batch)
|
|
10
|
+
Results: (*batch, ...). Singular information matrices (condition number above
|
|
11
|
+
1 / machine epsilon) give NaN.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import jax
|
|
15
|
+
import jax.numpy as jnp
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def crlb(J, *, W=None, sigma2=1, log=False):
|
|
19
|
+
"""Cramer-Rao lower bound: sum of the weighted variance bounds
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
J: Jacobian (nobs, *batch, nparam)
|
|
23
|
+
W: weights of the parameters (nparam,), default: 1
|
|
24
|
+
sigma2: noise variance
|
|
25
|
+
log: return log10 of the bound
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
(*batch,) bound
|
|
29
|
+
"""
|
|
30
|
+
bounds = crlb_split(J, W=W, sigma2=sigma2)
|
|
31
|
+
cost = jnp.sum(bounds, axis=-1)
|
|
32
|
+
return jnp.log10(cost) if log else cost
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def crlb_split(J, *, W=None, sigma2=1, log=False):
|
|
36
|
+
"""Cramer-Rao lower bound of each parameter
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
(*batch, nparam) weighted variance bounds
|
|
40
|
+
"""
|
|
41
|
+
lb = _inverse(_information(J) / sigma2)
|
|
42
|
+
bounds = jnp.diagonal(lb, axis1=-2, axis2=-1)
|
|
43
|
+
if W is not None:
|
|
44
|
+
bounds = bounds * jnp.asarray(W)
|
|
45
|
+
return jnp.log10(bounds) if log else bounds
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def confint(obs, pred, J, H=None, *, conflevel=0.95):
|
|
49
|
+
"""confidence intervals of the parameters and band of the prediction (delta method)
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
obs, pred: observed and predicted signals (nobs, *batch)
|
|
53
|
+
J: Jacobian of the prediction (nobs, *batch, nparam)
|
|
54
|
+
H: Hessian of the prediction (nobs, *batch, nparam, nparam), optional:
|
|
55
|
+
covariance from the Hessian of the least-squares cost instead of
|
|
56
|
+
the Gauss-Newton approximation
|
|
57
|
+
conflevel: confidence level
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
intervals (*batch, nparam), band (nobs, *batch): half-widths
|
|
61
|
+
"""
|
|
62
|
+
J = jnp.asarray(J)
|
|
63
|
+
nobs, nparam = J.shape[0], J.shape[-1]
|
|
64
|
+
dof = nobs - nparam
|
|
65
|
+
res = jnp.asarray(obs) - jnp.asarray(pred)
|
|
66
|
+
sse = jnp.sum(jnp.abs(res) ** 2, axis=0)
|
|
67
|
+
|
|
68
|
+
# Hessian of the cost sum |res|^2 / 2 w.r.t. the parameters
|
|
69
|
+
info = _information(J)
|
|
70
|
+
if H is not None:
|
|
71
|
+
info = info - jnp.einsum("n...,n...pq->...pq", res.conj(), H).real
|
|
72
|
+
cov = _inverse(info) * (sse / dof)[..., None, None]
|
|
73
|
+
|
|
74
|
+
tval = t_quantile((1 + conflevel) / 2, dof)
|
|
75
|
+
intervals = tval * jnp.sqrt(jnp.diagonal(cov, axis1=-2, axis2=-1))
|
|
76
|
+
band = tval * jnp.sqrt(jnp.einsum("n...p,...pq,n...q->n...", J.conj(), cov, J).real)
|
|
77
|
+
return intervals, band
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def t_quantile(p, dof):
|
|
81
|
+
"""quantile of Student's t distribution (p > 1/2), without scipy
|
|
82
|
+
|
|
83
|
+
CDF from the regularized incomplete beta function, inverted by bisection.
|
|
84
|
+
"""
|
|
85
|
+
p, dof = jnp.asarray(p, dtype=float), jnp.asarray(dof, dtype=float)
|
|
86
|
+
|
|
87
|
+
def cdf(t):
|
|
88
|
+
return 1 - 0.5 * jax.scipy.special.betainc(dof / 2, 0.5, dof / (dof + t**2))
|
|
89
|
+
|
|
90
|
+
def bisect(_, bounds):
|
|
91
|
+
low, high = bounds
|
|
92
|
+
mid = (low + high) / 2
|
|
93
|
+
above = cdf(mid) > p
|
|
94
|
+
return jnp.where(above, low, mid), jnp.where(above, mid, high)
|
|
95
|
+
|
|
96
|
+
low, high = jax.lax.fori_loop(
|
|
97
|
+
0, 100, bisect, (jnp.zeros_like(p), jnp.full_like(p, 1e6))
|
|
98
|
+
)
|
|
99
|
+
return (low + high) / 2
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
#
|
|
103
|
+
# private
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _information(J):
|
|
107
|
+
"""Fisher information J^H J, real part (*batch, nparam, nparam)"""
|
|
108
|
+
J = jnp.asarray(J)
|
|
109
|
+
return jnp.einsum("n...p,n...q->...pq", J.conj(), J).real
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _inverse(matrix):
|
|
113
|
+
"""inverse, NaN for singular matrices"""
|
|
114
|
+
cond = jnp.linalg.cond(matrix)
|
|
115
|
+
singular = ~(cond < 1 / jnp.finfo(matrix.dtype).eps) # also NaN/inf
|
|
116
|
+
inverse = jnp.linalg.inv(matrix)
|
|
117
|
+
return jnp.where(singular[..., None, None], jnp.nan, inverse)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epgx
|
|
3
|
+
Version: 0.1.dev0
|
|
4
|
+
Summary: Extended Phase Graph (EPG) simulations in JAX
|
|
5
|
+
Author: Pierre-Yves Baudin
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Repository, https://github.com/py-baudin/epgx
|
|
8
|
+
Keywords: MRI,extended phase graph,EPG,simulation,JAX,differentiable
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: jax
|
|
19
|
+
Requires-Dist: numpy
|
|
20
|
+
Provides-Extra: test
|
|
21
|
+
Requires-Dist: pytest; extra == "test"
|
|
22
|
+
Provides-Extra: plot
|
|
23
|
+
Requires-Dist: matplotlib; extra == "plot"
|
|
24
|
+
Requires-Dist: cmocean; extra == "plot"
|
|
25
|
+
Provides-Extra: xarray
|
|
26
|
+
Requires-Dist: xarray; extra == "xarray"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: epgx[plot,test,xarray]; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# epgx
|
|
33
|
+
|
|
34
|
+
A JAX implementation of the Extended Phase Graph (EPG) formalism, successor of
|
|
35
|
+
[epgpy](https://github.com/py-baudin/epgpy).
|
|
36
|
+
|
|
37
|
+
Work in progress: the API may change between versions.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
``` shell
|
|
42
|
+
pip install epgx # core (jax, numpy)
|
|
43
|
+
pip install epgx[plot] # with EPG diagrams (matplotlib, cmocean)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Development:
|
|
47
|
+
|
|
48
|
+
``` shell
|
|
49
|
+
conda create -n epgx python=3.12
|
|
50
|
+
conda activate epgx
|
|
51
|
+
pip install -e .[dev]
|
|
52
|
+
pip install --group parity # epgpy, for the parity tests (tests/parity)
|
|
53
|
+
pytest
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Precision
|
|
57
|
+
|
|
58
|
+
`epgx` follows JAX's precision setting (float32 by default) and never changes it.
|
|
59
|
+
For float64, enable it at the start of your program:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import jax
|
|
63
|
+
jax.config.update("jax_enable_x64", True)
|
|
64
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
epgx/__init__.py,sha256=3aDyN8qXa_0Zd-P8X5XDNyDrynI8lx1f7zRJHOidVvs,588
|
|
2
|
+
epgx/axes.py,sha256=U_VfGBsJCSuQu6mEbO1TR83AmcGlaI5nqovgNUEfQMY,10248
|
|
3
|
+
epgx/common.py,sha256=34x4UaU5edDfkBOA41yoSPLt4RbNAu5OJpQ-umXzNbo,2055
|
|
4
|
+
epgx/compiler.py,sha256=-M-8ILKbpmlH5qjANbTajQxgR6lamVsGRp9k7ZbcWnc,11986
|
|
5
|
+
epgx/diff.py,sha256=BFACor42z9ibVrE4jc_zON3yOItCgYoGsS3t_l0Nwkk,8855
|
|
6
|
+
epgx/examples.py,sha256=ZGauA9QKsd7uyzJEhGshyC1kPx6DXmCl5Ye8X1qPmVo,4888
|
|
7
|
+
epgx/functions.py,sha256=-UkbcBPLUNW09TA-GInhlt-KUhKZqsDrlxW2ROerWdA,22506
|
|
8
|
+
epgx/lowering.py,sha256=oKTkbCL40J6-hnWeoVoOJ26txMZZXzyszHsbdg2l2BA,3107
|
|
9
|
+
epgx/merging.py,sha256=gsoiEYeXbm6v_S_5bf8V2WFVdMhi-V-somJMqu0t5gI,4560
|
|
10
|
+
epgx/methods.py,sha256=XTwZWctRGAduaVHi80rHEcEcAIIJbQj4tskx_vtRhIA,12740
|
|
11
|
+
epgx/plotting.py,sha256=Ww9LAuCZvHmfY0c90tprZVHEqJymjdRLrsRLR9gWAMI,8219
|
|
12
|
+
epgx/progress.py,sha256=Nv1L-CaEC2-Y6bIwYvci8dky7YWCPN-u6xO90XJznjA,2752
|
|
13
|
+
epgx/sample.py,sha256=Pb_lVea2-1MBiPNXvdKQDhg8IiwqaI2nHEnVFihVFdQ,4506
|
|
14
|
+
epgx/sequences.py,sha256=_IzV4_DzwLWAm4AzXASmGT4pe2_btZb-A6jS1xgzj7M,32483
|
|
15
|
+
epgx/stats.py,sha256=nmVUEPrLep7vju1hORdmLVOUUIlxFhDIql3xK2KbDhw,3792
|
|
16
|
+
epgx/kernels/__init__.py,sha256=gfuTvWuy8Z_byah2rY6wv4_nmYcinf7zll2HKN3LYJs,356
|
|
17
|
+
epgx/kernels/affine.py,sha256=tzlMSwKONd1uzfOxjuDbDTroFNuDsdktWTEoM0hjyy8,6168
|
|
18
|
+
epgx/kernels/evolution.py,sha256=0MrFtXVUHQKQxOCsTRStvYipwCpp4XR70eBqSWh0Ox8,1449
|
|
19
|
+
epgx/kernels/operator.py,sha256=t4HDsFdmQzkvlWHL7z4_eiE985nNguSWK2YsLVhdch4,1139
|
|
20
|
+
epgx/kernels/probe.py,sha256=0lwX_k7sFatbVc7z5xew-59lqUl7ccUPqP9If-4wrnI,3865
|
|
21
|
+
epgx/kernels/shift.py,sha256=_vYxLjnOkSGLCy3FIOSmL0cKr6uKVQ9ITqFgOgkqpfw,3464
|
|
22
|
+
epgx/kernels/state.py,sha256=yuVfaa-_K1R1A6dZA7lObchRrbM1qoKPNoBVg0A3Eiw,16289
|
|
23
|
+
epgx/kernels/transition.py,sha256=cuQ3xfM9NUs89nkKfSMK3xTIs_SGvlMkV45nL--rJuU,1279
|
|
24
|
+
epgx/library/__init__.py,sha256=KVvf8PLA1_0fV5Hh3nn7OnOtheAJxEq5RYB9MuOHwlI,109
|
|
25
|
+
epgx/library/sequences.py,sha256=njOKEmyHMG3omb0y-f6PRbUBe3nqOxwRntvGOyuqpv4,7461
|
|
26
|
+
epgx/ops/__init__.py,sha256=Z-SMFeGhEt4no3gcWTwqtPUelpJ3iEzRM2YEAOPI9cY,593
|
|
27
|
+
epgx/ops/dephasing.py,sha256=A9dhsGbXIDsnI_SN4CsQcN_wON1QX5G46YD0H71e-j0,2752
|
|
28
|
+
epgx/ops/evolution.py,sha256=JkPnFhP6g4Iywt4N9YcsbWqeATKoX5RH0E9Uve36vM4,4434
|
|
29
|
+
epgx/ops/functions.py,sha256=rqmLt9Kj3XFHz4UT-BK17jrhWmp4d51n9wVJG38XPaw,5291
|
|
30
|
+
epgx/ops/operator.py,sha256=N5vg9S2qYegcM9z-anAxdyS6O-qp2_XOfJNMNdd4RCg,7290
|
|
31
|
+
epgx/ops/probe.py,sha256=X5W_281dALFRDRfHqaJD2qm8sZ7VhlM49B-D51R00UE,3912
|
|
32
|
+
epgx/ops/shift.py,sha256=UcNKgygVN3BjUaElUafxne-7KRhwNjYjyqnHKxGh_Lk,4303
|
|
33
|
+
epgx/ops/transition.py,sha256=M00yhd-9GRR1CCuk4fhcgCJOCwDBS8gUdyapHjxKB8c,1491
|
|
34
|
+
epgx-0.1.dev0.dist-info/licenses/LICENSE,sha256=TLhi4uBEksu2oRv12uW3Ar9k9OPMF_cqyVANPiP6uwA,1501
|
|
35
|
+
epgx-0.1.dev0.dist-info/METADATA,sha256=fWwymCNLH1o6Ca-dOTc_62NVC2as9mdpyfK8cABXcEo,1838
|
|
36
|
+
epgx-0.1.dev0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
37
|
+
epgx-0.1.dev0.dist-info/top_level.txt,sha256=mvERoxaeTHo7FgZKTjnE3kn5MKwvIVYiIyKZisyqVMU,5
|
|
38
|
+
epgx-0.1.dev0.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2026, py-baudin
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
epgx
|