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,485 @@
1
+ """Core kernel registry implementation.
2
+
3
+ Provides in-memory registry for kernel management with dependency resolution,
4
+ configuration APIs, and metadata management. The registry serves as the central
5
+ component for kernel discovery, validation, and execution orchestration.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import inspect
11
+ import logging
12
+ from copy import deepcopy
13
+ from functools import wraps
14
+ from typing import TYPE_CHECKING, Any
15
+
16
+ from ordered_set import OrderedSet
17
+
18
+ from .config import KernelConfig
19
+ from .exceptions import (
20
+ CircularDependencyError,
21
+ DependencyNotFoundError,
22
+ InvalidConfiguration,
23
+ )
24
+ from .execution import KernelApplyLevel, KernelExecutionProtocol, KernelRestrictions
25
+ from .metadata import KernelInfo, KernelMetadata
26
+
27
+ if TYPE_CHECKING:
28
+ from collections.abc import Callable
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class KernelRegistry:
34
+ """In-memory registry of kernels with dependency resolution and config APIs.
35
+
36
+ Central registry managing kernel registration, metadata storage, dependency
37
+ resolution, and configuration management. Provides APIs for kernel discovery,
38
+ validation, and execution preparation.
39
+
40
+ The registry maintains kernel metadata including signatures, dependencies,
41
+ execution protocols, and configuration parameters. It supports dependency
42
+ resolution with circular dependency detection and caching for performance.
43
+ """
44
+
45
+ def __init__(self) -> None:
46
+ """Initialize empty registry with metadata storage and resolution cache."""
47
+ self._metadata: dict[str, KernelMetadata] = {}
48
+ self._resolve_cache: dict[str, tuple[str, ...]] = {}
49
+
50
+ @staticmethod
51
+ def _split_signature(
52
+ func: Callable[..., Any],
53
+ ) -> tuple[tuple[str, ...], KernelConfig]:
54
+ """Split function signature into required fields and default config.
55
+
56
+ Analyzes function signature to extract parameter names without defaults
57
+ as required fields, and parameters with defaults as configuration options.
58
+
59
+ Args:
60
+ func: Function to analyze.
61
+
62
+ Returns:
63
+ Tuple of (required field names, configuration with defaults).
64
+
65
+ Raises:
66
+ TypeError: If function signature contains *args or **kwargs.
67
+ """
68
+ sig = inspect.signature(func)
69
+
70
+ req: list[str] = []
71
+ defs: dict[str, Any] = {}
72
+ for name, param in sig.parameters.items():
73
+ if param.kind in (
74
+ inspect.Parameter.VAR_POSITIONAL,
75
+ inspect.Parameter.VAR_KEYWORD,
76
+ ):
77
+ msg = "Kernels may not use *args or **kwargs."
78
+ raise TypeError(msg)
79
+
80
+ if param.default is inspect.Parameter.empty:
81
+ req.append(name)
82
+ else:
83
+ defs[name] = param.default
84
+
85
+ return tuple(req), KernelConfig(**defs)
86
+
87
+ def register_kernel(
88
+ self,
89
+ *,
90
+ name: str,
91
+ require_fields: tuple[str, ...],
92
+ produce_fields: tuple[str, ...],
93
+ implementation: Callable[..., Any],
94
+ apply_level: KernelApplyLevel,
95
+ execution_protocol: KernelExecutionProtocol | None,
96
+ restrictions: KernelRestrictions | None,
97
+ config: KernelConfig,
98
+ info: KernelInfo | None = None,
99
+ ) -> None:
100
+ """Register a kernel and its metadata in the registry.
101
+
102
+ Stores complete kernel metadata including implementation, dependencies,
103
+ execution parameters, and configuration. Clears dependency resolution
104
+ cache to ensure fresh dependency analysis.
105
+
106
+ Args:
107
+ name: Unique kernel identifier.
108
+ require_fields: Field names required as kernel input.
109
+ produce_fields: Field names produced by kernel.
110
+ implementation: Callable implementing kernel logic.
111
+ apply_level: Execution level (parameter/model/cross-model).
112
+ execution_protocol: Execution strategy (sequential/parallel).
113
+ restrictions: Optional argument restrictions.
114
+ config: Configuration parameters with defaults.
115
+ info: Optional documentation metadata.
116
+ """
117
+ if not isinstance(config, KernelConfig):
118
+ config = KernelConfig(**(config or {}))
119
+ cfg = deepcopy(config)
120
+ info = KernelInfo() if info is None else info
121
+ meta = KernelMetadata(
122
+ name,
123
+ require_fields,
124
+ produce_fields,
125
+ implementation,
126
+ apply_level,
127
+ execution_protocol,
128
+ restrictions,
129
+ cfg,
130
+ info,
131
+ )
132
+ self._metadata[name] = meta
133
+ self._resolve_cache.clear()
134
+ logger.debug(
135
+ "Registered kernel '%s' (requires=%s, produce=%s)",
136
+ name,
137
+ require_fields,
138
+ produce_fields,
139
+ )
140
+
141
+ def configure_kernel(self, name: str, conf: KernelConfig) -> None:
142
+ """Update kernel configuration values safely.
143
+
144
+ Merges provided configuration with existing kernel configuration,
145
+ validating all parameters against registered defaults.
146
+
147
+ Args:
148
+ name: Registered kernel name.
149
+ conf: Configuration updates to apply.
150
+
151
+ Raises:
152
+ DependencyNotFoundError: If kernel is not registered.
153
+ InvalidConfiguration: If configuration contains invalid parameters.
154
+ """
155
+ meta = self._get(name)
156
+ meta.config.update(conf)
157
+ logger.debug("Configured kernel '%s' with %s", name, conf.as_dict())
158
+
159
+ def _get(self, name: str) -> KernelMetadata:
160
+ """Get kernel metadata by name with error handling.
161
+
162
+ Args:
163
+ name: Kernel name to retrieve.
164
+
165
+ Returns:
166
+ Kernel metadata.
167
+
168
+ Raises:
169
+ DependencyNotFoundError: If kernel is not registered.
170
+ """
171
+ try:
172
+ return self._metadata[name]
173
+ except KeyError as e:
174
+ msg = f"Kernel '{name}' not registered"
175
+ raise DependencyNotFoundError(msg) from e
176
+
177
+ def list_kernels(self, verbose: bool = False) -> list[str]:
178
+ """List all registered kernels.
179
+
180
+ Args:
181
+ verbose: If True, return detailed string representations.
182
+
183
+ Returns:
184
+ List of kernel names or detailed representations.
185
+ """
186
+ return sorted(
187
+ [str(m) for m in self._metadata.values()]
188
+ if verbose
189
+ else list(self._metadata.keys())
190
+ )
191
+
192
+ def list_fields_can_produce(self, verbose: bool = False) -> list[str]:
193
+ """List all fields that registered kernels can produce.
194
+
195
+ Args:
196
+ verbose: If True, annotate each field with its producing kernel.
197
+
198
+ Returns:
199
+ Sorted list of field names, or "field <- kernel" strings.
200
+ """
201
+ can_produce_fields: list[str] = []
202
+ for kernel_name, kernel_meta in self._metadata.items():
203
+ can_produce_fields.extend(
204
+ f"{field} <- {kernel_name}" if verbose else field
205
+ for field in kernel_meta.produce_fields
206
+ )
207
+ return sorted(can_produce_fields)
208
+
209
+ def has_kernel(self, name: str) -> bool:
210
+ """Check if a kernel is registered.
211
+
212
+ Args:
213
+ name: Kernel name to check.
214
+
215
+ Returns:
216
+ True if kernel is registered, False otherwise.
217
+ """
218
+ return name in self._metadata
219
+
220
+ def can_produce_field(self, name: str) -> bool:
221
+ """Check if any registered kernel can produce a field.
222
+
223
+ Args:
224
+ name: Field name to check.
225
+
226
+ Returns:
227
+ True if any kernel can produce the field, False otherwise.
228
+ """
229
+ return name in self.list_fields_can_produce()
230
+
231
+ def get_kernel_apply_level(self, name: str) -> KernelApplyLevel:
232
+ """Get kernel application level.
233
+
234
+ Args:
235
+ name: Registered kernel name.
236
+
237
+ Returns:
238
+ Kernel application level.
239
+
240
+ Raises:
241
+ DependencyNotFoundError: If kernel is not registered.
242
+ """
243
+ return self._get(name).apply_level
244
+
245
+ def get_kernel_execution_protocol(self, name: str) -> KernelExecutionProtocol:
246
+ """Get kernel execution protocol.
247
+
248
+ Args:
249
+ name: Registered kernel name.
250
+
251
+ Returns:
252
+ Kernel execution protocol.
253
+
254
+ Raises:
255
+ DependencyNotFoundError: If kernel is not registered.
256
+ """
257
+ return self._get(name).execution_protocol
258
+
259
+ def get_fields_kernel_require(self, name: str) -> tuple[str, ...]:
260
+ """Get field names required by a kernel.
261
+
262
+ Args:
263
+ name: Registered kernel name.
264
+
265
+ Returns:
266
+ Tuple of required field names.
267
+
268
+ Raises:
269
+ DependencyNotFoundError: If kernel is not registered.
270
+ """
271
+ return self._get(name).require_fields
272
+
273
+ def get_fields_kernel_produce(self, name: str) -> tuple[str, ...]:
274
+ """Get field names produced by a kernel.
275
+
276
+ Args:
277
+ name: Registered kernel name.
278
+
279
+ Returns:
280
+ Tuple of produced field names.
281
+
282
+ Raises:
283
+ DependencyNotFoundError: If kernel is not registered.
284
+ """
285
+ return self._get(name).produce_fields
286
+
287
+ def get_kernel_config(self, name: str) -> dict[str, Any]:
288
+ """Get kernel configuration as dictionary.
289
+
290
+ Args:
291
+ name: Registered kernel name.
292
+
293
+ Returns:
294
+ Dictionary of configuration parameters and values.
295
+
296
+ Raises:
297
+ DependencyNotFoundError: If kernel is not registered.
298
+ """
299
+ return self._get(name).config.as_dict()
300
+
301
+ def get_kernel_restrictions(self, name: str) -> KernelRestrictions:
302
+ """Get kernel argument restrictions.
303
+
304
+ Args:
305
+ name: Registered kernel name.
306
+
307
+ Returns:
308
+ Kernel restrictions flags.
309
+
310
+ Raises:
311
+ DependencyNotFoundError: If kernel is not registered.
312
+ """
313
+ return self._get(name).restrictions
314
+
315
+ def get_kernel_implementation(self, name: str) -> Callable[..., Any]:
316
+ """Get kernel implementation wrapped with configuration injection.
317
+
318
+ Returns a wrapped version of the kernel implementation that automatically
319
+ injects configuration parameters as keyword arguments during execution.
320
+
321
+ Args:
322
+ name: Registered kernel name.
323
+
324
+ Returns:
325
+ Wrapped kernel implementation with config injection.
326
+
327
+ Raises:
328
+ DependencyNotFoundError: If kernel is not registered.
329
+ """
330
+ impl = self._get(name).implementation
331
+ cfg = self.get_kernel_config(name)
332
+
333
+ @wraps(impl)
334
+ def wrapped(*args: Any, **kwargs: Any) -> Any:
335
+ merged = {**cfg, **kwargs}
336
+ return impl(*args, **merged)
337
+
338
+ return wrapped
339
+
340
+ def get_kernel_info(self, name: str) -> KernelInfo:
341
+ """Get kernel documentation metadata.
342
+
343
+ Args:
344
+ name: Registered kernel name.
345
+
346
+ Returns:
347
+ Kernel documentation information.
348
+
349
+ Raises:
350
+ DependencyNotFoundError: If kernel is not registered.
351
+ """
352
+ return self._get(name).info
353
+
354
+ def resolve_dependencies(
355
+ self, field_or_kernel_name: str, visited: set[str] | None = None
356
+ ) -> tuple[str, ...]:
357
+ """Resolve dependencies producing required fields for a kernel or field name.
358
+
359
+ Recursively resolves all kernel dependencies required to produce the
360
+ specified field or execute the specified kernel. Uses caching for
361
+ performance and detects circular dependencies.
362
+
363
+ Args:
364
+ field_or_kernel_name: Kernel name or field name to resolve.
365
+ visited: Set of visited kernels for circular dependency detection.
366
+
367
+ Returns:
368
+ Tuple of kernel names in dependency order.
369
+
370
+ Raises:
371
+ CircularDependencyError: If circular dependency is detected.
372
+ ValueError: If no kernel can produce the specified field.
373
+ """
374
+ if field_or_kernel_name not in self._metadata:
375
+ kernel_name = self.get_kernel_producing_field(
376
+ field_name=field_or_kernel_name
377
+ )
378
+ else:
379
+ kernel_name = field_or_kernel_name
380
+
381
+ if kernel_name in self._resolve_cache:
382
+ return self._resolve_cache[kernel_name]
383
+
384
+ if visited is None:
385
+ visited = set()
386
+
387
+ if kernel_name in visited:
388
+ msg = f"Circular dependency for '{kernel_name}'"
389
+ raise CircularDependencyError(msg)
390
+
391
+ visited.add(kernel_name)
392
+
393
+ meta = self._get(kernel_name)
394
+ dependencies: OrderedSet[str] = OrderedSet()
395
+ for required in meta.require_fields:
396
+ for other_kernel in self.list_kernels():
397
+ if kernel_name == other_kernel:
398
+ continue
399
+ other_kernel_meta = self._get(other_kernel)
400
+ if required in other_kernel_meta.produce_fields:
401
+ dependencies.update(
402
+ self.resolve_dependencies(other_kernel, visited)
403
+ )
404
+ dependencies.add(kernel_name)
405
+
406
+ result = tuple(dependencies)
407
+ self._resolve_cache[kernel_name] = result
408
+ return result
409
+
410
+ def get_kernel_producing_field(self, field_name: str) -> str:
411
+ """Find kernel that produces a specific field.
412
+
413
+ Args:
414
+ field_name: Name of the field to find a producer for.
415
+
416
+ Returns:
417
+ Name of kernel that produces the field.
418
+
419
+ Raises:
420
+ ValueError: If no kernel produces the specified field.
421
+ """
422
+ for kernel in self.list_kernels():
423
+ kernel_meta = self._get(kernel)
424
+ if field_name in kernel_meta.produce_fields:
425
+ return kernel
426
+ raise ValueError
427
+
428
+ def resolve_produced_fields(self, field_or_kernel_name: str) -> set[str]:
429
+ """Get all fields that will be produced when computing a field or kernel.
430
+
431
+ Resolves the dependency chain and collects all fields produced by
432
+ all kernels in the chain (including intermediate dependencies).
433
+
434
+ Args:
435
+ field_or_kernel_name: Field name or kernel name to resolve.
436
+
437
+ Returns:
438
+ Set of all field names that will be produced.
439
+ """
440
+ kernel_chain = self.resolve_dependencies(field_or_kernel_name)
441
+ produced: set[str] = set()
442
+ for kernel_name in kernel_chain:
443
+ produced.update(self._get(kernel_name).produce_fields)
444
+ return produced
445
+
446
+ def normalize_kernel_result(self, kernel_name: str, result: Any) -> dict[str, Any]:
447
+ """Normalize kernel outputs to a mapping of produced fields to values.
448
+
449
+ Converts kernel return values to a standardized dictionary format mapping
450
+ field names to values, handling various return types (dict, tuple, scalar).
451
+
452
+ Args:
453
+ kernel_name: Name of kernel that produced the result.
454
+ result: Raw kernel return value.
455
+
456
+ Returns:
457
+ Dictionary mapping produced field names to values.
458
+
459
+ Raises:
460
+ InvalidConfiguration: If result format doesn't match kernel specification.
461
+ DependencyNotFoundError: If kernel is not registered.
462
+ """
463
+ meta = self._get(kernel_name)
464
+
465
+ if isinstance(result, dict):
466
+ return result
467
+
468
+ if isinstance(result, tuple):
469
+ if len(result) != len(meta.produce_fields):
470
+ msg = (
471
+ f"Kernel '{kernel_name}' returned {len(result)} values but "
472
+ f"produce declares {len(meta.produce_fields)}"
473
+ )
474
+ raise InvalidConfiguration(msg)
475
+ return dict(zip(meta.produce_fields, result, strict=False))
476
+
477
+ if len(meta.produce_fields) == 1:
478
+ return {meta.produce_fields[0]: result}
479
+
480
+ msg = (
481
+ f"Kernel '{kernel_name}' returned scalar but produce declares multiple "
482
+ "fields: "
483
+ f"{meta.produce_fields}"
484
+ )
485
+ raise InvalidConfiguration(msg)
@@ -0,0 +1,147 @@
1
+ """Centralized constants for magic strings used throughout diffract.
2
+
3
+ This module defines all special string patterns and field names to avoid
4
+ scattered string literals across the codebase.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ # --- Filter pattern prefix ---
10
+ REGEX_PREFIX = "re:"
11
+ """Prefix for regex patterns in filter functions (e.g., 're:layer\\.\\d+')."""
12
+
13
+ # --- Reserved field names ---
14
+ WEIGHTS_FIELD = "weights"
15
+ """Field name for storing parameter weight arrays."""
16
+
17
+ # --- HDF5 storage internals ---
18
+ HDF5_INDEX_GROUP = "__index__"
19
+ """HDF5 group name for storing object index datasets."""
20
+
21
+ HDF5_INDEX_DATASET = "all_objs"
22
+ """HDF5 dataset name within index group for object UIDs."""
23
+
24
+ HDF5_INDEX_TOMBSTONE = ""
25
+ """Marker value for deleted entries in HDF5 index."""
26
+
27
+ # --- Storage attribute names ---
28
+ STORAGE_ATTR_TYPE = "type"
29
+ """Attribute name for stored value type."""
30
+
31
+ STORAGE_ATTR_META = "value_meta"
32
+ """Attribute name for stored value metadata."""
33
+
34
+ # --- Zarr storage internals ---
35
+
36
+ ZARR_INDEX_GROUP = "_index"
37
+ """Zarr group name for storing object and field index datasets."""
38
+
39
+ ZARR_INDEX_OBJS = "objs"
40
+ """Zarr dataset name for object UID index."""
41
+
42
+ ZARR_INDEX_FIELDS = "fields"
43
+ """Zarr dataset name for field name index."""
44
+
45
+ # --- Contextual field naming for aggregations ---
46
+ CONTEXT_SEPARATOR = "@"
47
+ """Separator between field name and context suffix (e.g., 'metric@models[...]')."""
48
+
49
+ MODELS_CONTEXT_PREFIX = "models"
50
+ """Context key for model identifiers in aggregated field names."""
51
+
52
+ PARAMS_CONTEXT_PREFIX = "params"
53
+ """Context key for parameter names in aggregated field names."""
54
+
55
+ # --- Progress bar settings ---
56
+ PROGRESS_BAR_DELAY_SEC = 1.0
57
+ """Delay before showing progress bar (seconds). Avoids clutter for fast ops."""
58
+
59
+ PROGRESS_BAR_MIN_ITEMS = 50
60
+ """Minimum items before showing a progress bar."""
61
+
62
+ # --- Storage table names ---
63
+ TABLE_PARAMETERS = "parameters"
64
+ """Table name for neural network parameter data."""
65
+
66
+ TABLE_AGGREGATES = "aggregates"
67
+ """Table name for aggregate data between parameters/models."""
68
+
69
+
70
+ def format_context_part(key: str, values: tuple[str, ...]) -> str:
71
+ """Format a single context part like 'models[m1,m2]'."""
72
+ return f"{key}[{','.join(sorted(values))}]"
73
+
74
+
75
+ def format_field_suffix(
76
+ models: tuple[str, ...] | None = None,
77
+ params: tuple[str, ...] | None = None,
78
+ ) -> str:
79
+ """Build a deterministic suffix from context identifiers.
80
+
81
+ Args:
82
+ models: Model identifiers participating in aggregation.
83
+ params: Parameter names participating in aggregation.
84
+
85
+ Returns:
86
+ Formatted suffix like 'models[m1,m2]@params[p1,p2]' or empty string.
87
+ """
88
+ parts: list[str] = []
89
+ if models:
90
+ parts.append(format_context_part(MODELS_CONTEXT_PREFIX, models))
91
+ if params:
92
+ parts.append(format_context_part(PARAMS_CONTEXT_PREFIX, params))
93
+ return CONTEXT_SEPARATOR.join(parts) if parts else ""
94
+
95
+
96
+ def format_contextual_field_name(
97
+ field_name: str,
98
+ models: tuple[str, ...] | None = None,
99
+ params: tuple[str, ...] | None = None,
100
+ ) -> str:
101
+ """Compose contextual field name with optional context suffix.
102
+
103
+ Args:
104
+ field_name: Base field name.
105
+ models: Model identifiers for context.
106
+ params: Parameter names for context.
107
+
108
+ Returns:
109
+ Field name with context suffix if any context provided.
110
+ """
111
+ suffix = format_field_suffix(models, params)
112
+ return f"{field_name}{CONTEXT_SEPARATOR}{suffix}" if suffix else field_name
113
+
114
+
115
+ def parse_contextual_field_name(
116
+ full_field_name: str,
117
+ ) -> tuple[str, tuple[str, ...] | None, tuple[str, ...] | None]:
118
+ """Parse a contextual field name into base name and context components.
119
+
120
+ Args:
121
+ full_field_name: Field name like 'metric@models[m1,m2]@params[p1]'.
122
+
123
+ Returns:
124
+ Tuple of (base_name, models, params) where models and params are None
125
+ if not present in the suffix.
126
+ """
127
+ import re
128
+
129
+ if CONTEXT_SEPARATOR not in full_field_name:
130
+ return full_field_name, None, None
131
+
132
+ parts = full_field_name.split(CONTEXT_SEPARATOR)
133
+ base_name = parts[0]
134
+
135
+ models: tuple[str, ...] | None = None
136
+ params: tuple[str, ...] | None = None
137
+
138
+ models_pattern = re.compile(rf"^{MODELS_CONTEXT_PREFIX}\[(.+)\]$")
139
+ params_pattern = re.compile(rf"^{PARAMS_CONTEXT_PREFIX}\[(.+)\]$")
140
+
141
+ for part in parts[1:]:
142
+ if m := models_pattern.match(part):
143
+ models = tuple(m.group(1).split(","))
144
+ elif m := params_pattern.match(part):
145
+ params = tuple(m.group(1).split(","))
146
+
147
+ return base_name, models, params
@@ -0,0 +1,32 @@
1
+ """Generic data abstractions for diffract.
2
+
3
+ This module provides domain-agnostic protocols and implementations for
4
+ data management, including:
5
+
6
+ - IMetadata: Protocol for serializable metadata with unique identifier
7
+ - IDataProxy: Protocol for lazy-loading storage-backed data
8
+ - IDataView: Protocol for batch operations on filtered collections
9
+ - IDataRepository: Protocol for ownership of storage/cache infrastructure
10
+
11
+ - DataProxy: Generic implementation of IDataProxy
12
+ - DataView: Generic implementation of IDataView
13
+ - DataRepository: Generic implementation of IDataRepository
14
+
15
+ These abstractions are specialized by domain-specific modules:
16
+ - core/data/nn/params: Parameter-specific implementations
17
+ - core/data/nn/relations: Relation-specific implementations
18
+ """
19
+
20
+ from .interface import EntityIndex, EntityUID, FieldName
21
+ from .proxy import DataProxy
22
+ from .repository import DataRepository
23
+ from .view import DataView
24
+
25
+ __all__ = [
26
+ "DataProxy",
27
+ "DataRepository",
28
+ "DataView",
29
+ "EntityIndex",
30
+ "EntityUID",
31
+ "FieldName",
32
+ ]