diffract-core 0.2.1__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.
- diffract/__init__.py +52 -0
- diffract/configs/fast_speed_without_disk.ini +48 -0
- diffract/configs/hybrid.ini +74 -0
- diffract/configs/sqlite.ini +62 -0
- diffract/containers.py +405 -0
- diffract/core/__init__.py +23 -0
- diffract/core/cache/__init__.py +24 -0
- diffract/core/cache/containers.py +93 -0
- diffract/core/cache/interface.py +117 -0
- diffract/core/cache/redis_manager.py +464 -0
- diffract/core/cache/simple_manager.py +478 -0
- diffract/core/compute/__init__.py +46 -0
- diffract/core/compute/config.py +48 -0
- diffract/core/compute/containers.py +81 -0
- diffract/core/compute/decorator.py +125 -0
- diffract/core/compute/exceptions.py +31 -0
- diffract/core/compute/execution/__init__.py +14 -0
- diffract/core/compute/execution/_types.py +70 -0
- diffract/core/compute/execution/aggregation.py +74 -0
- diffract/core/compute/execution/aggregation_runner.py +362 -0
- diffract/core/compute/execution/enums.py +38 -0
- diffract/core/compute/execution/executor.py +125 -0
- diffract/core/compute/execution/parameter_runner.py +125 -0
- diffract/core/compute/execution/restrictions.py +61 -0
- diffract/core/compute/execution/strategy.py +118 -0
- diffract/core/compute/execution/utils.py +112 -0
- diffract/core/compute/extensions/__init__.py +0 -0
- diffract/core/compute/extensions/power_law.py +1051 -0
- diffract/core/compute/extensions/rmt.py +150 -0
- diffract/core/compute/extensions/utils.py +36 -0
- diffract/core/compute/field_signature.py +183 -0
- diffract/core/compute/kernels/__init__.py +48 -0
- diffract/core/compute/kernels/alignment.py +106 -0
- diffract/core/compute/kernels/heavy_tailed.py +394 -0
- diffract/core/compute/kernels/marchenko_pastur.py +99 -0
- diffract/core/compute/kernels/mat_decomposition.py +101 -0
- diffract/core/compute/kernels/mat_properties.py +53 -0
- diffract/core/compute/kernels/model_quality.py +27 -0
- diffract/core/compute/kernels/norms.py +88 -0
- diffract/core/compute/kernels/ranks.py +35 -0
- diffract/core/compute/kernels/tracy_widom.py +36 -0
- diffract/core/compute/metadata.py +67 -0
- diffract/core/compute/registry.py +485 -0
- diffract/core/constants.py +147 -0
- diffract/core/data/__init__.py +32 -0
- diffract/core/data/interface.py +304 -0
- diffract/core/data/metadata/__init__.py +15 -0
- diffract/core/data/metadata/containers.py +60 -0
- diffract/core/data/metadata/interface.py +208 -0
- diffract/core/data/metadata/sqlite_index.py +604 -0
- diffract/core/data/nn/__init__.py +43 -0
- diffract/core/data/nn/aggregates/__init__.py +35 -0
- diffract/core/data/nn/aggregates/metadata.py +96 -0
- diffract/core/data/nn/aggregates/proxy.py +57 -0
- diffract/core/data/nn/aggregates/repository.py +87 -0
- diffract/core/data/nn/aggregates/view.py +307 -0
- diffract/core/data/nn/containers.py +86 -0
- diffract/core/data/nn/extractors/__init__.py +49 -0
- diffract/core/data/nn/extractors/base.py +300 -0
- diffract/core/data/nn/extractors/factory.py +234 -0
- diffract/core/data/nn/extractors/flax.py +112 -0
- diffract/core/data/nn/extractors/handlers/__init__.py +39 -0
- diffract/core/data/nn/extractors/handlers/base.py +110 -0
- diffract/core/data/nn/extractors/handlers/flax_handlers.py +71 -0
- diffract/core/data/nn/extractors/handlers/numpy_handlers.py +63 -0
- diffract/core/data/nn/extractors/handlers/onnx_handlers.py +67 -0
- diffract/core/data/nn/extractors/handlers/tensorflow_handlers.py +82 -0
- diffract/core/data/nn/extractors/handlers/torch_handlers.py +124 -0
- diffract/core/data/nn/extractors/interface.py +56 -0
- diffract/core/data/nn/extractors/numpy.py +94 -0
- diffract/core/data/nn/extractors/onnx.py +108 -0
- diffract/core/data/nn/extractors/tensorflow.py +99 -0
- diffract/core/data/nn/extractors/torch.py +204 -0
- diffract/core/data/nn/params/__init__.py +27 -0
- diffract/core/data/nn/params/interface.py +402 -0
- diffract/core/data/nn/params/metadata.py +110 -0
- diffract/core/data/nn/params/proxy.py +93 -0
- diffract/core/data/nn/params/repository.py +45 -0
- diffract/core/data/nn/params/schema.py +82 -0
- diffract/core/data/nn/params/view.py +393 -0
- diffract/core/data/proxy.py +246 -0
- diffract/core/data/repository.py +227 -0
- diffract/core/data/utils.py +33 -0
- diffract/core/data/view.py +389 -0
- diffract/core/export/__init__.py +27 -0
- diffract/core/export/containers.py +47 -0
- diffract/core/export/exporters.py +191 -0
- diffract/core/export/formatters/__init__.py +23 -0
- diffract/core/export/formatters/base.py +68 -0
- diffract/core/export/formatters/dict_formatter.py +38 -0
- diffract/core/export/formatters/json_formatter.py +58 -0
- diffract/core/export/formatters/list_formatter.py +41 -0
- diffract/core/export/formatters/pandas_formatter.py +86 -0
- diffract/core/export/formatters/polars_formatter.py +86 -0
- diffract/core/export/formatters/registry.py +84 -0
- diffract/core/export/interface.py +105 -0
- diffract/core/parallel/__init__.py +21 -0
- diffract/core/parallel/containers.py +57 -0
- diffract/core/parallel/execution.py +91 -0
- diffract/core/parallel/runtime.py +91 -0
- diffract/core/storage/__init__.py +35 -0
- diffract/core/storage/base_manager.py +330 -0
- diffract/core/storage/containers.py +122 -0
- diffract/core/storage/hdf5_manager.py +856 -0
- diffract/core/storage/hybrid_manager.py +218 -0
- diffract/core/storage/interface.py +222 -0
- diffract/core/storage/metadata.py +89 -0
- diffract/core/storage/ram_manager.py +159 -0
- diffract/core/storage/sqlite_manager.py +1062 -0
- diffract/core/storage/zarr_manager.py +992 -0
- diffract/core/utils/__init__.py +45 -0
- diffract/core/utils/build.py +46 -0
- diffract/core/utils/exceptions.py +11 -0
- diffract/core/utils/hashing.py +90 -0
- diffract/core/utils/imports.py +292 -0
- diffract/core/utils/math.py +15 -0
- diffract/py.typed +0 -0
- diffract/session/__init__.py +24 -0
- diffract/session/errors.py +17 -0
- diffract/session/field_cache.py +216 -0
- diffract/session/namespaces/__init__.py +15 -0
- diffract/session/namespaces/compute/__init__.py +219 -0
- diffract/session/namespaces/models/__init__.py +282 -0
- diffract/session/namespaces/models/parameters/__init__.py +133 -0
- diffract/session/namespaces/models/parameters/meta_patcher.py +167 -0
- diffract/session/namespaces/results/__init__.py +378 -0
- diffract/session/namespaces/results/eraser.py +129 -0
- diffract/session/namespaces/results/injester.py +249 -0
- diffract/session/namespaces/utils/__init__.py +141 -0
- diffract/session/namespaces/utils/merger.py +295 -0
- diffract/session/namespaces/viz/__init__.py +91 -0
- diffract/session/namespaces/viz/_utils.py +14 -0
- diffract/session/namespaces/viz/box.py +207 -0
- diffract/session/namespaces/viz/grid.py +160 -0
- diffract/session/namespaces/viz/heatmap.py +164 -0
- diffract/session/namespaces/viz/scatter.py +202 -0
- diffract/session/namespaces/viz/sparkline.py +270 -0
- diffract/session/namespaces/viz/violin.py +212 -0
- diffract/session/session.py +260 -0
- diffract/session/utils.py +81 -0
- diffract/viz/__init__.py +42 -0
- diffract/viz/data/__init__.py +34 -0
- diffract/viz/data/detection.py +57 -0
- diffract/viz/data/extraction.py +117 -0
- diffract/viz/data/filtering.py +57 -0
- diffract/viz/data/ordering.py +129 -0
- diffract/viz/data/provider.py +99 -0
- diffract/viz/data/types.py +98 -0
- diffract/viz/plots/__init__.py +37 -0
- diffract/viz/plots/base/__init__.py +20 -0
- diffract/viz/plots/base/axis.py +219 -0
- diffract/viz/plots/base/coloraxis.py +133 -0
- diffract/viz/plots/base/configurator.py +18 -0
- diffract/viz/plots/base/jitter.py +368 -0
- diffract/viz/plots/base/line.py +197 -0
- diffract/viz/plots/base/marker.py +278 -0
- diffract/viz/plots/base/overlay.py +14 -0
- diffract/viz/plots/base/plot.py +110 -0
- diffract/viz/plots/base/update.py +50 -0
- diffract/viz/plots/boxplot.py +283 -0
- diffract/viz/plots/cluster.py +374 -0
- diffract/viz/plots/heatmap.py +168 -0
- diffract/viz/plots/scatter.py +233 -0
- diffract/viz/plots/sparkline.py +405 -0
- diffract/viz/plots/subplots/__init__.py +32 -0
- diffract/viz/plots/subplots/coloraxis.py +339 -0
- diffract/viz/plots/subplots/factory.py +713 -0
- diffract/viz/plots/subplots/grid.py +214 -0
- diffract/viz/plots/subplots/layout.py +370 -0
- diffract/viz/plots/subplots/spec.py +39 -0
- diffract/viz/plots/violin.py +298 -0
- diffract/viz/renderer.py +513 -0
- diffract/viz/styling/__init__.py +78 -0
- diffract/viz/styling/palettes/__init__.py +20 -0
- diffract/viz/styling/palettes/bundle.py +16 -0
- diffract/viz/styling/palettes/color.py +83 -0
- diffract/viz/styling/palettes/dashes.py +27 -0
- diffract/viz/styling/palettes/symbols.py +42 -0
- diffract/viz/styling/resolvers/__init__.py +11 -0
- diffract/viz/styling/resolvers/categorical.py +68 -0
- diffract/viz/styling/resolvers/color.py +77 -0
- diffract/viz/styling/resolvers/numeric.py +108 -0
- diffract/viz/styling/sources.py +27 -0
- diffract/viz/styling/theme/__init__.py +29 -0
- diffract/viz/styling/theme/applier.py +114 -0
- diffract/viz/styling/theme/components.py +66 -0
- diffract/viz/styling/theme/presets.py +58 -0
- diffract/viz/styling/theme/theme.py +36 -0
- diffract_core-0.2.1.dist-info/METADATA +530 -0
- diffract_core-0.2.1.dist-info/RECORD +193 -0
- diffract_core-0.2.1.dist-info/WHEEL +4 -0
- diffract_core-0.2.1.dist-info/licenses/LICENSE +201 -0
- diffract_core-0.2.1.dist-info/licenses/NOTICE +2 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Random matrix theory distributions for the spectral kernels.
|
|
2
|
+
|
|
3
|
+
Implements the closed-form cumulative distribution function of the
|
|
4
|
+
Marchenko-Pastur law and the quantile function of the Tracy-Widom law for
|
|
5
|
+
beta = 1 (GOE). The Marchenko-Pastur CDF integrates the density through the
|
|
6
|
+
standard antiderivative of ``sqrt((x - a)(b - x)) / x``. The Tracy-Widom
|
|
7
|
+
distribution is computed from its Painleve II representation: the
|
|
8
|
+
Hastings-McLeod solution ``q'' = s q + 2 q^3`` with ``q(s) ~ Ai(s)`` as
|
|
9
|
+
``s -> +inf`` is integrated numerically together with the auxiliary
|
|
10
|
+
integrals that give ``F2(s) = exp(-int (x - s) q^2 dx)`` and
|
|
11
|
+
``F1(s)^2 = F2(s) exp(-int q dx)``; quantiles invert the resulting CDF with
|
|
12
|
+
a monotone PCHIP spline built once per process.
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
- Marchenko, V. A. and Pastur, L. A. "Distribution of eigenvalues for
|
|
16
|
+
some sets of random matrices". Mathematics of the USSR-Sbornik (1967).
|
|
17
|
+
- Tracy, C. A. and Widom, H. "Level-spacing distributions and the Airy
|
|
18
|
+
kernel". Communications in Mathematical Physics (1994).
|
|
19
|
+
- Tracy, C. A. and Widom, H. "On orthogonal and symplectic matrix
|
|
20
|
+
ensembles". Communications in Mathematical Physics (1996).
|
|
21
|
+
- Hastings, S. P. and McLeod, J. B. "A boundary value problem associated
|
|
22
|
+
with the second Painleve transcendent and the Korteweg-de Vries
|
|
23
|
+
equation". Archive for Rational Mechanics and Analysis (1980).
|
|
24
|
+
- Bejan, A. "Largest eigenvalues and sample covariance matrices".
|
|
25
|
+
M.Sc. dissertation, The University of Warwick (2005).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from functools import cache
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
import numpy as np
|
|
34
|
+
from numpy.typing import NDArray
|
|
35
|
+
from scipy.integrate import quad, solve_ivp
|
|
36
|
+
from scipy.interpolate import PchipInterpolator
|
|
37
|
+
from scipy.special import airy
|
|
38
|
+
|
|
39
|
+
_PAINLEVE_S_START = 10.0
|
|
40
|
+
_PAINLEVE_S_END = -5.0
|
|
41
|
+
_PAINLEVE_RTOL = 1e-12
|
|
42
|
+
_PAINLEVE_ATOL = 1e-18
|
|
43
|
+
_TW_GRID_STEP = 0.005
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def marchenko_pastur_cdf(
|
|
47
|
+
x: NDArray[np.floating[Any]] | float,
|
|
48
|
+
ratio: float,
|
|
49
|
+
sigma: float = 1.0,
|
|
50
|
+
) -> NDArray[np.float64]:
|
|
51
|
+
"""Evaluate the Marchenko-Pastur CDF for beta = 1.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
x: Value or array of values to evaluate the CDF at.
|
|
55
|
+
ratio: Dimension ratio lambda in (0, 1] of the underlying matrix.
|
|
56
|
+
sigma: Standard deviation of the matrix entries.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
CDF values as a float64 array of the same shape as ``x``.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ValueError: If ``ratio`` is outside (0, 1] or ``sigma`` is not positive.
|
|
63
|
+
"""
|
|
64
|
+
if not 0 < ratio <= 1:
|
|
65
|
+
raise ValueError(f"ratio must be in (0, 1], got {ratio}")
|
|
66
|
+
if sigma <= 0:
|
|
67
|
+
raise ValueError(f"sigma must be positive, got {sigma}")
|
|
68
|
+
|
|
69
|
+
sqrt_ratio = np.sqrt(ratio)
|
|
70
|
+
edge_low = (1 - sqrt_ratio) ** 2
|
|
71
|
+
edge_high = (1 + sqrt_ratio) ** 2
|
|
72
|
+
|
|
73
|
+
z = np.asarray(x, dtype=np.float64) / sigma**2
|
|
74
|
+
result = np.zeros_like(z)
|
|
75
|
+
result[z >= edge_high] = 1.0
|
|
76
|
+
|
|
77
|
+
inside = (z > edge_low) & (z < edge_high)
|
|
78
|
+
if np.any(inside):
|
|
79
|
+
zi = z[inside]
|
|
80
|
+
root = np.sqrt((zi - edge_low) * (edge_high - zi))
|
|
81
|
+
span = edge_high - edge_low
|
|
82
|
+
first_arcsin = np.arcsin((2 * zi - edge_low - edge_high) / span)
|
|
83
|
+
product = edge_low * edge_high
|
|
84
|
+
second_arcsin = np.arcsin(
|
|
85
|
+
((edge_low + edge_high) * zi - 2 * product) / (zi * span)
|
|
86
|
+
)
|
|
87
|
+
result[inside] = (
|
|
88
|
+
root
|
|
89
|
+
+ (1 + ratio) * first_arcsin
|
|
90
|
+
- (1 - ratio) * second_arcsin
|
|
91
|
+
+ np.pi * ratio
|
|
92
|
+
) / (2 * np.pi * ratio)
|
|
93
|
+
|
|
94
|
+
return np.clip(result, 0.0, 1.0)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@cache
|
|
98
|
+
def _tw_beta1_cdf_spline() -> PchipInterpolator:
|
|
99
|
+
s_start = _PAINLEVE_S_START
|
|
100
|
+
ai_start, ai_prime_start, *_ = airy(s_start)
|
|
101
|
+
airy_tail_sq = quad(lambda t: airy(t)[0] ** 2, s_start, np.inf)[0]
|
|
102
|
+
airy_tail_weighted = quad(
|
|
103
|
+
lambda t: (t - s_start) * airy(t)[0] ** 2, s_start, np.inf
|
|
104
|
+
)[0]
|
|
105
|
+
airy_tail = quad(lambda t: airy(t)[0], s_start, np.inf)[0]
|
|
106
|
+
|
|
107
|
+
def rhs(s: float, y: NDArray[np.float64]) -> list[float]:
|
|
108
|
+
q, q_prime, _, k_val, _ = y
|
|
109
|
+
return [q_prime, s * q + 2 * q**3, -k_val, -(q**2), -q]
|
|
110
|
+
|
|
111
|
+
solution = solve_ivp(
|
|
112
|
+
rhs,
|
|
113
|
+
[s_start, _PAINLEVE_S_END],
|
|
114
|
+
[ai_start, ai_prime_start, airy_tail_weighted, airy_tail_sq, airy_tail],
|
|
115
|
+
method="DOP853",
|
|
116
|
+
rtol=_PAINLEVE_RTOL,
|
|
117
|
+
atol=_PAINLEVE_ATOL,
|
|
118
|
+
dense_output=True,
|
|
119
|
+
)
|
|
120
|
+
if solution.status != 0:
|
|
121
|
+
raise RuntimeError(f"Painleve II integration failed: {solution.message}")
|
|
122
|
+
|
|
123
|
+
s_grid = np.arange(_PAINLEVE_S_END, s_start, _TW_GRID_STEP)
|
|
124
|
+
states = solution.sol(s_grid)
|
|
125
|
+
cdf = np.exp(-(states[2] + states[4]) / 2)
|
|
126
|
+
|
|
127
|
+
increasing = np.concatenate(([True], np.diff(cdf) > 0))
|
|
128
|
+
return PchipInterpolator(cdf[increasing], s_grid[increasing], extrapolate=False)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def tracy_widom_ppf(q: float) -> float:
|
|
132
|
+
"""Evaluate the Tracy-Widom (beta = 1) quantile function.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
q: Lower-tail probability inside the computed CDF range.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The quantile corresponding to ``q``.
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
ValueError: If ``q`` falls outside the computed CDF range.
|
|
142
|
+
"""
|
|
143
|
+
spline = _tw_beta1_cdf_spline()
|
|
144
|
+
q_low, q_high = spline.x[0], spline.x[-1]
|
|
145
|
+
if not q_low <= q <= q_high:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"q must be within the computed CDF range "
|
|
148
|
+
f"[{q_low:.2e}, {q_high:.10f}], got {q}"
|
|
149
|
+
)
|
|
150
|
+
return float(spline(q))
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
|
|
7
|
+
import diffract.core.utils.imports as import_utils
|
|
8
|
+
|
|
9
|
+
if not import_utils.is_available("torch"):
|
|
10
|
+
|
|
11
|
+
def torch_cuda_wrapper(*_args: Any, **_kwargs: Any) -> None:
|
|
12
|
+
"""Stub when torch is not available."""
|
|
13
|
+
raise ImportError
|
|
14
|
+
|
|
15
|
+
else:
|
|
16
|
+
torch = import_utils.require("torch")
|
|
17
|
+
|
|
18
|
+
def torch_cuda_wrapper(
|
|
19
|
+
array: NDArray[np.floating[Any]],
|
|
20
|
+
function: Callable[[torch.Tensor], torch.Tensor],
|
|
21
|
+
) -> Any:
|
|
22
|
+
"""Execute function on CUDA tensor and return to CPU."""
|
|
23
|
+
torch.cuda.empty_cache()
|
|
24
|
+
with torch.no_grad():
|
|
25
|
+
tensor = torch.Tensor(array).to("cuda")
|
|
26
|
+
output = function(tensor)
|
|
27
|
+
if isinstance(output, torch.Tensor):
|
|
28
|
+
output = output.to("cpu").float().numpy()
|
|
29
|
+
else:
|
|
30
|
+
output_container_type = type(output)
|
|
31
|
+
|
|
32
|
+
output_ = [item.to("cpu").float().numpy() for item in output]
|
|
33
|
+
output = output_container_type(output_)
|
|
34
|
+
|
|
35
|
+
del tensor
|
|
36
|
+
return output
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Utilities to describe stored fields (shape/kind/dtype) for plotting/inspection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from diffract.core.compute.execution.enums import KernelApplyLevel
|
|
11
|
+
from diffract.core.compute.registry import KernelRegistry
|
|
12
|
+
from diffract.core.storage.interface import IStorageManager
|
|
13
|
+
from diffract.core.storage.metadata import ValueMetadata, infer_value_metadata
|
|
14
|
+
from diffract.core.utils.exceptions import format_exception_message
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(slots=True)
|
|
20
|
+
class FieldSignature:
|
|
21
|
+
"""Metadata describing a stored field's characteristics."""
|
|
22
|
+
|
|
23
|
+
field: str
|
|
24
|
+
kind: str
|
|
25
|
+
dtype: str | None
|
|
26
|
+
shape: tuple[int, ...] | None
|
|
27
|
+
ndim: int | None
|
|
28
|
+
is_numeric: bool
|
|
29
|
+
apply_level: KernelApplyLevel | None = None
|
|
30
|
+
source: str | None = None # e.g., "metadata", "inferred"
|
|
31
|
+
available: bool = True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _signature_from_meta(
|
|
35
|
+
field: str,
|
|
36
|
+
meta: dict[str, Any],
|
|
37
|
+
*,
|
|
38
|
+
apply_level: KernelApplyLevel | None = None,
|
|
39
|
+
source: str = "metadata",
|
|
40
|
+
) -> FieldSignature:
|
|
41
|
+
vm = ValueMetadata.from_jsonable(meta)
|
|
42
|
+
return FieldSignature(
|
|
43
|
+
field=field,
|
|
44
|
+
kind=vm.kind,
|
|
45
|
+
dtype=vm.dtype,
|
|
46
|
+
shape=vm.shape,
|
|
47
|
+
ndim=vm.ndim,
|
|
48
|
+
is_numeric=vm.is_numeric,
|
|
49
|
+
apply_level=apply_level,
|
|
50
|
+
source=source,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _signature_from_value(
|
|
55
|
+
field: str,
|
|
56
|
+
value: Any,
|
|
57
|
+
*,
|
|
58
|
+
apply_level: KernelApplyLevel | None = None,
|
|
59
|
+
source: str = "value",
|
|
60
|
+
) -> FieldSignature:
|
|
61
|
+
vm = infer_value_metadata(value)
|
|
62
|
+
return FieldSignature(
|
|
63
|
+
field=field,
|
|
64
|
+
kind=vm.kind,
|
|
65
|
+
dtype=vm.dtype,
|
|
66
|
+
shape=vm.shape,
|
|
67
|
+
ndim=vm.ndim,
|
|
68
|
+
is_numeric=vm.is_numeric,
|
|
69
|
+
apply_level=apply_level,
|
|
70
|
+
source=source,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def collect_field_signatures(
|
|
75
|
+
storage: IStorageManager,
|
|
76
|
+
field_names: Iterable[str] | None = None,
|
|
77
|
+
*,
|
|
78
|
+
sample_limit: int = 8,
|
|
79
|
+
apply_level_hint: KernelApplyLevel | None = None,
|
|
80
|
+
) -> dict[str, FieldSignature]:
|
|
81
|
+
"""Collect signatures for fields stored in a storage backend.
|
|
82
|
+
|
|
83
|
+
Sampling is limited to avoid loading all parameters.
|
|
84
|
+
"""
|
|
85
|
+
signatures: dict[str, FieldSignature] = {}
|
|
86
|
+
|
|
87
|
+
fields = list(field_names) if field_names is not None else storage.list_fields()
|
|
88
|
+
|
|
89
|
+
for field in fields:
|
|
90
|
+
if field in signatures:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
obj_ids = storage.list_objs_has_field(field)
|
|
95
|
+
except Exception as e: # noqa: BLE001
|
|
96
|
+
logger.debug(
|
|
97
|
+
"Failed to list objects for field '%s': %s",
|
|
98
|
+
field,
|
|
99
|
+
format_exception_message(e),
|
|
100
|
+
exc_info=True,
|
|
101
|
+
)
|
|
102
|
+
obj_ids = []
|
|
103
|
+
|
|
104
|
+
obj_ids = obj_ids[:sample_limit]
|
|
105
|
+
signature: FieldSignature | None = None
|
|
106
|
+
|
|
107
|
+
for obj_uid in obj_ids:
|
|
108
|
+
meta = storage.get_field_metadata(obj_uid, field)
|
|
109
|
+
if meta is not None:
|
|
110
|
+
signature = _signature_from_meta(
|
|
111
|
+
field, meta, apply_level=apply_level_hint, source="metadata"
|
|
112
|
+
)
|
|
113
|
+
break
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
value = storage.get_field(obj_uid, field)
|
|
117
|
+
except Exception as e: # noqa: BLE001
|
|
118
|
+
logger.debug(
|
|
119
|
+
"Failed to read field '%s' for object '%s': %s",
|
|
120
|
+
field,
|
|
121
|
+
obj_uid,
|
|
122
|
+
format_exception_message(e),
|
|
123
|
+
exc_info=True,
|
|
124
|
+
)
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
signature = _signature_from_value(
|
|
128
|
+
field, value, apply_level=apply_level_hint, source="value"
|
|
129
|
+
)
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
if signature is not None:
|
|
133
|
+
signatures[field] = signature
|
|
134
|
+
|
|
135
|
+
return signatures
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _registry_apply_levels(registry: KernelRegistry) -> dict[str, KernelApplyLevel]:
|
|
139
|
+
mapping: dict[str, KernelApplyLevel] = {}
|
|
140
|
+
for kernel_name in registry.list_kernels():
|
|
141
|
+
apply_level = registry.get_kernel_apply_level(kernel_name)
|
|
142
|
+
for field in registry.get_fields_kernel_produce(kernel_name):
|
|
143
|
+
mapping[field] = apply_level
|
|
144
|
+
return mapping
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def collect_field_catalog(
|
|
148
|
+
storage: IStorageManager,
|
|
149
|
+
registry: KernelRegistry,
|
|
150
|
+
field_names: Iterable[str] | None = None,
|
|
151
|
+
*,
|
|
152
|
+
sample_limit: int = 8,
|
|
153
|
+
) -> dict[str, FieldSignature]:
|
|
154
|
+
"""Merge observed signatures with registry hints.
|
|
155
|
+
|
|
156
|
+
Includes computable-only fields.
|
|
157
|
+
"""
|
|
158
|
+
observed = collect_field_signatures(
|
|
159
|
+
storage, field_names=field_names, sample_limit=sample_limit
|
|
160
|
+
)
|
|
161
|
+
apply_levels = _registry_apply_levels(registry)
|
|
162
|
+
|
|
163
|
+
# Enrich observed with apply_level if known.
|
|
164
|
+
for field, level in apply_levels.items():
|
|
165
|
+
if field in observed and observed[field].apply_level is None:
|
|
166
|
+
observed[field].apply_level = level
|
|
167
|
+
|
|
168
|
+
# Add registry-only fields (not yet computed).
|
|
169
|
+
for field, level in apply_levels.items():
|
|
170
|
+
if field not in observed:
|
|
171
|
+
observed[field] = FieldSignature(
|
|
172
|
+
field=field,
|
|
173
|
+
kind="object",
|
|
174
|
+
dtype=None,
|
|
175
|
+
shape=None,
|
|
176
|
+
ndim=None,
|
|
177
|
+
is_numeric=False,
|
|
178
|
+
apply_level=level,
|
|
179
|
+
source="registry",
|
|
180
|
+
available=False,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return observed
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from .alignment import (
|
|
2
|
+
avg_vector_agreement,
|
|
3
|
+
max_vector_agreement,
|
|
4
|
+
overlap,
|
|
5
|
+
vector_agreement,
|
|
6
|
+
)
|
|
7
|
+
from .heavy_tailed import (
|
|
8
|
+
expon_p_value,
|
|
9
|
+
exponential_fit,
|
|
10
|
+
ht_concentration,
|
|
11
|
+
ht_presence,
|
|
12
|
+
ht_scale,
|
|
13
|
+
pl_p_value,
|
|
14
|
+
power_law_fit,
|
|
15
|
+
tpl_p_value,
|
|
16
|
+
truncated_power_law_fit,
|
|
17
|
+
)
|
|
18
|
+
from .marchenko_pastur import (
|
|
19
|
+
marchenko_pastur_fit,
|
|
20
|
+
mp_concentration,
|
|
21
|
+
mp_ks,
|
|
22
|
+
mp_num_spikes,
|
|
23
|
+
mp_presence,
|
|
24
|
+
mp_sval_max,
|
|
25
|
+
)
|
|
26
|
+
from .mat_decomposition import esd, esd_max, esd_min, svd
|
|
27
|
+
from .mat_properties import (
|
|
28
|
+
aspect_ratio,
|
|
29
|
+
greater_dim,
|
|
30
|
+
lower_dim,
|
|
31
|
+
weights_rand,
|
|
32
|
+
weights_std,
|
|
33
|
+
)
|
|
34
|
+
from .model_quality import pl_alpha_weighted, rand_distance
|
|
35
|
+
from .norms import (
|
|
36
|
+
alpha_norm,
|
|
37
|
+
frob_norm,
|
|
38
|
+
l1_norm,
|
|
39
|
+
l2_norm,
|
|
40
|
+
log_norm,
|
|
41
|
+
log_spectral_norm,
|
|
42
|
+
model_alpha_norm,
|
|
43
|
+
param_norm,
|
|
44
|
+
prod_frob_norm,
|
|
45
|
+
prod_spectral_norm,
|
|
46
|
+
)
|
|
47
|
+
from .ranks import effective_rank, hard_rank, mp_soft_rank, stable_rank
|
|
48
|
+
from .tracy_widom import tw_esd_bound, tw_num_spikes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from typing import Any, cast
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numpy.typing import NDArray
|
|
5
|
+
|
|
6
|
+
from diffract.core.compute.decorator import kernel
|
|
7
|
+
from diffract.core.compute.execution.enums import (
|
|
8
|
+
KernelApplyLevel,
|
|
9
|
+
KernelRestrictions,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@kernel(
|
|
14
|
+
name="l_overlap",
|
|
15
|
+
require_fields=("weights_lsvs", "lower_dim"),
|
|
16
|
+
apply_level=KernelApplyLevel.CROSS_MODEL,
|
|
17
|
+
restrictions=KernelRestrictions.BINARY,
|
|
18
|
+
)
|
|
19
|
+
@kernel(
|
|
20
|
+
name="r_overlap",
|
|
21
|
+
require_fields=("weights_rsvs", "lower_dim"),
|
|
22
|
+
apply_level=KernelApplyLevel.CROSS_MODEL,
|
|
23
|
+
restrictions=KernelRestrictions.BINARY,
|
|
24
|
+
)
|
|
25
|
+
def overlap(
|
|
26
|
+
weights_svs: tuple[NDArray[np.floating[Any]], NDArray[np.floating[Any]]],
|
|
27
|
+
lower_dim: tuple[int, int],
|
|
28
|
+
*,
|
|
29
|
+
absolute: bool = False,
|
|
30
|
+
) -> NDArray[np.floating[Any]]:
|
|
31
|
+
"""Compute overlap matrix between two sets of singular vectors."""
|
|
32
|
+
svs, svs_other = weights_svs
|
|
33
|
+
|
|
34
|
+
l_dim, l_dim_other = lower_dim
|
|
35
|
+
if l_dim != l_dim_other:
|
|
36
|
+
msg = "l_dim and l_dim_other should be equal"
|
|
37
|
+
raise ValueError(msg)
|
|
38
|
+
|
|
39
|
+
result = (svs.T @ svs_other)[:l_dim, :l_dim]
|
|
40
|
+
|
|
41
|
+
if absolute:
|
|
42
|
+
result = np.abs(result)
|
|
43
|
+
|
|
44
|
+
return cast("NDArray[np.floating[Any]]", result)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@kernel(
|
|
48
|
+
name="l_agreement",
|
|
49
|
+
require_fields=("l_overlap",),
|
|
50
|
+
apply_level=KernelApplyLevel.CROSS_MODEL,
|
|
51
|
+
)
|
|
52
|
+
@kernel(
|
|
53
|
+
name="r_agreement",
|
|
54
|
+
require_fields=("r_overlap",),
|
|
55
|
+
apply_level=KernelApplyLevel.CROSS_MODEL,
|
|
56
|
+
)
|
|
57
|
+
def vector_agreement(overlap: NDArray[np.floating[Any]]) -> NDArray[np.floating[Any]]:
|
|
58
|
+
"""Return the diagonal of an overlap matrix (per-component agreement)."""
|
|
59
|
+
result = np.diag(overlap)
|
|
60
|
+
return cast("NDArray[np.floating[Any]]", result)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@kernel(
|
|
64
|
+
name="max_l_agreement",
|
|
65
|
+
require_fields=("l_overlap",),
|
|
66
|
+
apply_level=KernelApplyLevel.CROSS_MODEL,
|
|
67
|
+
)
|
|
68
|
+
@kernel(
|
|
69
|
+
name="max_r_agreement",
|
|
70
|
+
require_fields=("r_overlap",),
|
|
71
|
+
apply_level=KernelApplyLevel.CROSS_MODEL,
|
|
72
|
+
)
|
|
73
|
+
def max_vector_agreement(
|
|
74
|
+
overlap: NDArray[np.floating[Any]],
|
|
75
|
+
) -> NDArray[np.floating[Any]]:
|
|
76
|
+
"""Return max absolute overlap per row (best agreement per component)."""
|
|
77
|
+
result = np.max(np.abs(overlap), axis=1)
|
|
78
|
+
return cast("NDArray[np.floating[Any]]", result)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@kernel(
|
|
82
|
+
name="avg_max_l_agreement",
|
|
83
|
+
require_fields=("max_l_agreement",),
|
|
84
|
+
apply_level=KernelApplyLevel.CROSS_MODEL,
|
|
85
|
+
)
|
|
86
|
+
@kernel(
|
|
87
|
+
name="avg_max_r_agreement",
|
|
88
|
+
require_fields=("max_r_agreement",),
|
|
89
|
+
apply_level=KernelApplyLevel.CROSS_MODEL,
|
|
90
|
+
)
|
|
91
|
+
@kernel(
|
|
92
|
+
name="avg_l_agreement",
|
|
93
|
+
require_fields=("l_agreement",),
|
|
94
|
+
apply_level=KernelApplyLevel.CROSS_MODEL,
|
|
95
|
+
)
|
|
96
|
+
@kernel(
|
|
97
|
+
name="avg_r_agreement",
|
|
98
|
+
require_fields=("r_agreement",),
|
|
99
|
+
apply_level=KernelApplyLevel.CROSS_MODEL,
|
|
100
|
+
)
|
|
101
|
+
def avg_vector_agreement(
|
|
102
|
+
agreement: NDArray[np.floating[Any]],
|
|
103
|
+
) -> NDArray[np.floating[Any]]:
|
|
104
|
+
"""Return the mean of per-component agreement values."""
|
|
105
|
+
result = np.mean(agreement)
|
|
106
|
+
return cast("NDArray[np.floating[Any]]", result)
|