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.
Files changed (193) hide show
  1. diffract/__init__.py +52 -0
  2. diffract/configs/fast_speed_without_disk.ini +48 -0
  3. diffract/configs/hybrid.ini +74 -0
  4. diffract/configs/sqlite.ini +62 -0
  5. diffract/containers.py +405 -0
  6. diffract/core/__init__.py +23 -0
  7. diffract/core/cache/__init__.py +24 -0
  8. diffract/core/cache/containers.py +93 -0
  9. diffract/core/cache/interface.py +117 -0
  10. diffract/core/cache/redis_manager.py +464 -0
  11. diffract/core/cache/simple_manager.py +478 -0
  12. diffract/core/compute/__init__.py +46 -0
  13. diffract/core/compute/config.py +48 -0
  14. diffract/core/compute/containers.py +81 -0
  15. diffract/core/compute/decorator.py +125 -0
  16. diffract/core/compute/exceptions.py +31 -0
  17. diffract/core/compute/execution/__init__.py +14 -0
  18. diffract/core/compute/execution/_types.py +70 -0
  19. diffract/core/compute/execution/aggregation.py +74 -0
  20. diffract/core/compute/execution/aggregation_runner.py +362 -0
  21. diffract/core/compute/execution/enums.py +38 -0
  22. diffract/core/compute/execution/executor.py +125 -0
  23. diffract/core/compute/execution/parameter_runner.py +125 -0
  24. diffract/core/compute/execution/restrictions.py +61 -0
  25. diffract/core/compute/execution/strategy.py +118 -0
  26. diffract/core/compute/execution/utils.py +112 -0
  27. diffract/core/compute/extensions/__init__.py +0 -0
  28. diffract/core/compute/extensions/power_law.py +1051 -0
  29. diffract/core/compute/extensions/rmt.py +150 -0
  30. diffract/core/compute/extensions/utils.py +36 -0
  31. diffract/core/compute/field_signature.py +183 -0
  32. diffract/core/compute/kernels/__init__.py +48 -0
  33. diffract/core/compute/kernels/alignment.py +106 -0
  34. diffract/core/compute/kernels/heavy_tailed.py +394 -0
  35. diffract/core/compute/kernels/marchenko_pastur.py +99 -0
  36. diffract/core/compute/kernels/mat_decomposition.py +101 -0
  37. diffract/core/compute/kernels/mat_properties.py +53 -0
  38. diffract/core/compute/kernels/model_quality.py +27 -0
  39. diffract/core/compute/kernels/norms.py +88 -0
  40. diffract/core/compute/kernels/ranks.py +35 -0
  41. diffract/core/compute/kernels/tracy_widom.py +36 -0
  42. diffract/core/compute/metadata.py +67 -0
  43. diffract/core/compute/registry.py +485 -0
  44. diffract/core/constants.py +147 -0
  45. diffract/core/data/__init__.py +32 -0
  46. diffract/core/data/interface.py +304 -0
  47. diffract/core/data/metadata/__init__.py +15 -0
  48. diffract/core/data/metadata/containers.py +60 -0
  49. diffract/core/data/metadata/interface.py +208 -0
  50. diffract/core/data/metadata/sqlite_index.py +604 -0
  51. diffract/core/data/nn/__init__.py +43 -0
  52. diffract/core/data/nn/aggregates/__init__.py +35 -0
  53. diffract/core/data/nn/aggregates/metadata.py +96 -0
  54. diffract/core/data/nn/aggregates/proxy.py +57 -0
  55. diffract/core/data/nn/aggregates/repository.py +87 -0
  56. diffract/core/data/nn/aggregates/view.py +307 -0
  57. diffract/core/data/nn/containers.py +86 -0
  58. diffract/core/data/nn/extractors/__init__.py +49 -0
  59. diffract/core/data/nn/extractors/base.py +300 -0
  60. diffract/core/data/nn/extractors/factory.py +234 -0
  61. diffract/core/data/nn/extractors/flax.py +112 -0
  62. diffract/core/data/nn/extractors/handlers/__init__.py +39 -0
  63. diffract/core/data/nn/extractors/handlers/base.py +110 -0
  64. diffract/core/data/nn/extractors/handlers/flax_handlers.py +71 -0
  65. diffract/core/data/nn/extractors/handlers/numpy_handlers.py +63 -0
  66. diffract/core/data/nn/extractors/handlers/onnx_handlers.py +67 -0
  67. diffract/core/data/nn/extractors/handlers/tensorflow_handlers.py +82 -0
  68. diffract/core/data/nn/extractors/handlers/torch_handlers.py +124 -0
  69. diffract/core/data/nn/extractors/interface.py +56 -0
  70. diffract/core/data/nn/extractors/numpy.py +94 -0
  71. diffract/core/data/nn/extractors/onnx.py +108 -0
  72. diffract/core/data/nn/extractors/tensorflow.py +99 -0
  73. diffract/core/data/nn/extractors/torch.py +204 -0
  74. diffract/core/data/nn/params/__init__.py +27 -0
  75. diffract/core/data/nn/params/interface.py +402 -0
  76. diffract/core/data/nn/params/metadata.py +110 -0
  77. diffract/core/data/nn/params/proxy.py +93 -0
  78. diffract/core/data/nn/params/repository.py +45 -0
  79. diffract/core/data/nn/params/schema.py +82 -0
  80. diffract/core/data/nn/params/view.py +393 -0
  81. diffract/core/data/proxy.py +246 -0
  82. diffract/core/data/repository.py +227 -0
  83. diffract/core/data/utils.py +33 -0
  84. diffract/core/data/view.py +389 -0
  85. diffract/core/export/__init__.py +27 -0
  86. diffract/core/export/containers.py +47 -0
  87. diffract/core/export/exporters.py +191 -0
  88. diffract/core/export/formatters/__init__.py +23 -0
  89. diffract/core/export/formatters/base.py +68 -0
  90. diffract/core/export/formatters/dict_formatter.py +38 -0
  91. diffract/core/export/formatters/json_formatter.py +58 -0
  92. diffract/core/export/formatters/list_formatter.py +41 -0
  93. diffract/core/export/formatters/pandas_formatter.py +86 -0
  94. diffract/core/export/formatters/polars_formatter.py +86 -0
  95. diffract/core/export/formatters/registry.py +84 -0
  96. diffract/core/export/interface.py +105 -0
  97. diffract/core/parallel/__init__.py +21 -0
  98. diffract/core/parallel/containers.py +57 -0
  99. diffract/core/parallel/execution.py +91 -0
  100. diffract/core/parallel/runtime.py +91 -0
  101. diffract/core/storage/__init__.py +35 -0
  102. diffract/core/storage/base_manager.py +330 -0
  103. diffract/core/storage/containers.py +122 -0
  104. diffract/core/storage/hdf5_manager.py +856 -0
  105. diffract/core/storage/hybrid_manager.py +218 -0
  106. diffract/core/storage/interface.py +222 -0
  107. diffract/core/storage/metadata.py +89 -0
  108. diffract/core/storage/ram_manager.py +159 -0
  109. diffract/core/storage/sqlite_manager.py +1062 -0
  110. diffract/core/storage/zarr_manager.py +992 -0
  111. diffract/core/utils/__init__.py +45 -0
  112. diffract/core/utils/build.py +46 -0
  113. diffract/core/utils/exceptions.py +11 -0
  114. diffract/core/utils/hashing.py +90 -0
  115. diffract/core/utils/imports.py +292 -0
  116. diffract/core/utils/math.py +15 -0
  117. diffract/py.typed +0 -0
  118. diffract/session/__init__.py +24 -0
  119. diffract/session/errors.py +17 -0
  120. diffract/session/field_cache.py +216 -0
  121. diffract/session/namespaces/__init__.py +15 -0
  122. diffract/session/namespaces/compute/__init__.py +219 -0
  123. diffract/session/namespaces/models/__init__.py +282 -0
  124. diffract/session/namespaces/models/parameters/__init__.py +133 -0
  125. diffract/session/namespaces/models/parameters/meta_patcher.py +167 -0
  126. diffract/session/namespaces/results/__init__.py +378 -0
  127. diffract/session/namespaces/results/eraser.py +129 -0
  128. diffract/session/namespaces/results/injester.py +249 -0
  129. diffract/session/namespaces/utils/__init__.py +141 -0
  130. diffract/session/namespaces/utils/merger.py +295 -0
  131. diffract/session/namespaces/viz/__init__.py +91 -0
  132. diffract/session/namespaces/viz/_utils.py +14 -0
  133. diffract/session/namespaces/viz/box.py +207 -0
  134. diffract/session/namespaces/viz/grid.py +160 -0
  135. diffract/session/namespaces/viz/heatmap.py +164 -0
  136. diffract/session/namespaces/viz/scatter.py +202 -0
  137. diffract/session/namespaces/viz/sparkline.py +270 -0
  138. diffract/session/namespaces/viz/violin.py +212 -0
  139. diffract/session/session.py +260 -0
  140. diffract/session/utils.py +81 -0
  141. diffract/viz/__init__.py +42 -0
  142. diffract/viz/data/__init__.py +34 -0
  143. diffract/viz/data/detection.py +57 -0
  144. diffract/viz/data/extraction.py +117 -0
  145. diffract/viz/data/filtering.py +57 -0
  146. diffract/viz/data/ordering.py +129 -0
  147. diffract/viz/data/provider.py +99 -0
  148. diffract/viz/data/types.py +98 -0
  149. diffract/viz/plots/__init__.py +37 -0
  150. diffract/viz/plots/base/__init__.py +20 -0
  151. diffract/viz/plots/base/axis.py +219 -0
  152. diffract/viz/plots/base/coloraxis.py +133 -0
  153. diffract/viz/plots/base/configurator.py +18 -0
  154. diffract/viz/plots/base/jitter.py +368 -0
  155. diffract/viz/plots/base/line.py +197 -0
  156. diffract/viz/plots/base/marker.py +278 -0
  157. diffract/viz/plots/base/overlay.py +14 -0
  158. diffract/viz/plots/base/plot.py +110 -0
  159. diffract/viz/plots/base/update.py +50 -0
  160. diffract/viz/plots/boxplot.py +283 -0
  161. diffract/viz/plots/cluster.py +374 -0
  162. diffract/viz/plots/heatmap.py +168 -0
  163. diffract/viz/plots/scatter.py +233 -0
  164. diffract/viz/plots/sparkline.py +405 -0
  165. diffract/viz/plots/subplots/__init__.py +32 -0
  166. diffract/viz/plots/subplots/coloraxis.py +339 -0
  167. diffract/viz/plots/subplots/factory.py +713 -0
  168. diffract/viz/plots/subplots/grid.py +214 -0
  169. diffract/viz/plots/subplots/layout.py +370 -0
  170. diffract/viz/plots/subplots/spec.py +39 -0
  171. diffract/viz/plots/violin.py +298 -0
  172. diffract/viz/renderer.py +513 -0
  173. diffract/viz/styling/__init__.py +78 -0
  174. diffract/viz/styling/palettes/__init__.py +20 -0
  175. diffract/viz/styling/palettes/bundle.py +16 -0
  176. diffract/viz/styling/palettes/color.py +83 -0
  177. diffract/viz/styling/palettes/dashes.py +27 -0
  178. diffract/viz/styling/palettes/symbols.py +42 -0
  179. diffract/viz/styling/resolvers/__init__.py +11 -0
  180. diffract/viz/styling/resolvers/categorical.py +68 -0
  181. diffract/viz/styling/resolvers/color.py +77 -0
  182. diffract/viz/styling/resolvers/numeric.py +108 -0
  183. diffract/viz/styling/sources.py +27 -0
  184. diffract/viz/styling/theme/__init__.py +29 -0
  185. diffract/viz/styling/theme/applier.py +114 -0
  186. diffract/viz/styling/theme/components.py +66 -0
  187. diffract/viz/styling/theme/presets.py +58 -0
  188. diffract/viz/styling/theme/theme.py +36 -0
  189. diffract_core-0.2.1.dist-info/METADATA +530 -0
  190. diffract_core-0.2.1.dist-info/RECORD +193 -0
  191. diffract_core-0.2.1.dist-info/WHEEL +4 -0
  192. diffract_core-0.2.1.dist-info/licenses/LICENSE +201 -0
  193. 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)