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,216 @@
1
+ """Session-level cache for field availability.
2
+
3
+ Caches the result of list_fields_by_uid() to avoid repeated expensive
4
+ storage scans when multiple export_metrics() calls are made without
5
+ intervening compute.apply() or other mutation operations.
6
+
7
+ The cache uses a generation counter for lazy invalidation: when a mutation
8
+ occurs, the generation increments and cached data is considered stale.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ from collections.abc import Iterable
15
+ from dataclasses import dataclass, field
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class SessionFieldCache:
22
+ """Cache for field availability across session read operations.
23
+
24
+ This cache stores the mapping of parameter UID -> list of available fields,
25
+ avoiding repeated calls to list_fields_by_uid() which can be expensive
26
+ for large parameter collections.
27
+
28
+ The cache is automatically invalidated when mutating operations occur
29
+ (compute, add, erase, merge). It can also be incrementally updated
30
+ after compute() to avoid full rescans.
31
+
32
+ Example:
33
+ cache = SessionFieldCache()
34
+
35
+ # First export_metrics() populates the cache
36
+ fields_by_uid = view.list_fields_by_uid()
37
+ cache.set(fields_by_uid)
38
+
39
+ # Second export_metrics() uses cached data
40
+ if cache.is_valid:
41
+ fields_by_uid = cache.get()
42
+
43
+ # compute.apply() invalidates the cache
44
+ cache.invalidate()
45
+ """
46
+
47
+ _generation: int = field(default=0, repr=False)
48
+ _fields_by_uid: dict[str, list[str]] | None = field(default=None, repr=False)
49
+
50
+ def get(self) -> dict[str, list[str]] | None:
51
+ """Return cached field mapping or None if not populated.
52
+
53
+ Returns:
54
+ Dictionary mapping parameter UID to list of field names,
55
+ or None if cache is not populated.
56
+ """
57
+ return self._fields_by_uid
58
+
59
+ def set(self, fields_by_uid: dict[str, list[str]]) -> None:
60
+ """Store field mapping in cache.
61
+
62
+ Args:
63
+ fields_by_uid: Mapping of parameter UID to list of available fields.
64
+ """
65
+ self._fields_by_uid = fields_by_uid
66
+ logger.debug(
67
+ "Field cache populated with %d entries at generation %d",
68
+ len(fields_by_uid),
69
+ self._generation,
70
+ )
71
+
72
+ def update(self, fields_by_uid: dict[str, list[str]]) -> None:
73
+ """Merge additional field mappings into the existing cache.
74
+
75
+ Args:
76
+ fields_by_uid: Mapping of parameter UID to list of available
77
+ fields to merge into the current cache contents.
78
+ """
79
+ self._fields_by_uid.update(**fields_by_uid)
80
+
81
+ def invalidate(self) -> None:
82
+ """Invalidate cache (called after mutations).
83
+
84
+ Increments the generation counter and clears cached data.
85
+ This should be called after any operation that modifies storage:
86
+ compute.apply(), models.add(), models.erase(), results.erase(),
87
+ utils.merge_other_session().
88
+ """
89
+ self._fields_by_uid = None
90
+ self._generation += 1
91
+ logger.debug("Field cache invalidated, new generation: %d", self._generation)
92
+
93
+ def add_computed_fields(
94
+ self,
95
+ affected_uids: Iterable[str],
96
+ new_fields: Iterable[str],
97
+ ) -> None:
98
+ """Incrementally update cache after compute.apply() operation.
99
+
100
+ Instead of full invalidation, this method adds newly computed fields
101
+ to existing cache entries. This is an optimization for cases where
102
+ we know exactly which fields were added.
103
+
104
+ If cache is not populated, this is a no-op.
105
+
106
+ Args:
107
+ affected_uids: UIDs of parameters that were computed.
108
+ new_fields: Field names that were produced by the computation.
109
+ """
110
+ if self._fields_by_uid is None:
111
+ logger.debug("Skipping incremental update: cache not populated")
112
+ return
113
+
114
+ new_fields_set = set(new_fields)
115
+ updated_count = 0
116
+
117
+ for uid in affected_uids:
118
+ if uid in self._fields_by_uid:
119
+ existing = set(self._fields_by_uid[uid])
120
+ if not new_fields_set.issubset(existing):
121
+ existing.update(new_fields_set)
122
+ self._fields_by_uid[uid] = sorted(existing)
123
+ updated_count += 1
124
+
125
+ logger.debug(
126
+ "Incrementally updated %d cache entries with fields: %s",
127
+ updated_count,
128
+ ", ".join(sorted(new_fields_set)),
129
+ )
130
+
131
+ def remove_fields_by_uids(
132
+ self, uids: Iterable[str], fields_to_remove: Iterable[str]
133
+ ) -> None:
134
+ """Remove fields from cache entries after results.erase().
135
+
136
+ Instead of full invalidation, this method removes specified fields
137
+ from specified cache entries. This is an optimization for results.erase().
138
+
139
+ If cache is not populated, this is a no-op.
140
+
141
+ Args:
142
+ uids: UIDs of cache entries to remove fields from.
143
+ fields_to_remove: Field names that were erased.
144
+ """
145
+ if self._fields_by_uid is None:
146
+ logger.debug("Skipping field removal: cache not populated")
147
+ return
148
+
149
+ fields_set = set(fields_to_remove)
150
+ updated_count = 0
151
+
152
+ for uid in uids:
153
+ original_len = len(self._fields_by_uid[uid])
154
+ self._fields_by_uid[uid] = [
155
+ f for f in self._fields_by_uid[uid] if f not in fields_set
156
+ ]
157
+ if len(self._fields_by_uid[uid]) != original_len:
158
+ updated_count += 1
159
+
160
+ logger.debug(
161
+ "Removed fields from %d cache entries: %s",
162
+ updated_count,
163
+ ", ".join(sorted(fields_set)),
164
+ )
165
+
166
+ def remove_uids(self, uids_to_remove: Iterable[str]) -> None:
167
+ """Remove UIDs from cache after models.erase().
168
+
169
+ Instead of full invalidation, this method removes specified UIDs
170
+ from the cache. This is an optimization for models.erase().
171
+
172
+ If cache is not populated, this is a no-op.
173
+
174
+ Args:
175
+ uids_to_remove: UIDs that were erased from storage.
176
+ """
177
+ if self._fields_by_uid is None:
178
+ logger.debug("Skipping UID removal: cache not populated")
179
+ return
180
+
181
+ removed_count = 0
182
+ for uid in uids_to_remove:
183
+ if uid in self._fields_by_uid:
184
+ del self._fields_by_uid[uid]
185
+ removed_count += 1
186
+
187
+ logger.debug("Removed %d UIDs from cache", removed_count)
188
+
189
+ @property
190
+ def is_valid(self) -> bool:
191
+ """Check if cache contains valid data.
192
+
193
+ Returns:
194
+ True if cache is populated and ready for use.
195
+ """
196
+ return self._fields_by_uid is not None
197
+
198
+ @property
199
+ def generation(self) -> int:
200
+ """Current generation counter.
201
+
202
+ Increments on each invalidation. Useful for debugging.
203
+
204
+ Returns:
205
+ Current generation number.
206
+ """
207
+ return self._generation
208
+
209
+ def clear(self) -> None:
210
+ """Clear cache completely and reset generation.
211
+
212
+ Use this for full session reset, not for normal invalidation.
213
+ """
214
+ self._fields_by_uid = None
215
+ self._generation = 0
216
+ logger.debug("Field cache cleared and reset")
@@ -0,0 +1,15 @@
1
+ """Namespace helpers for Session facade."""
2
+
3
+ from .compute import ComputeNamespace
4
+ from .models import ModelsNamespace
5
+ from .results import ResultsNamespace
6
+ from .utils import UtilsNamespace
7
+ from .viz import VizNamespace
8
+
9
+ __all__ = [
10
+ "ComputeNamespace",
11
+ "ModelsNamespace",
12
+ "ResultsNamespace",
13
+ "UtilsNamespace",
14
+ "VizNamespace",
15
+ ]
@@ -0,0 +1,219 @@
1
+ """Compute namespace for Session."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import difflib
6
+ import logging
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from dependency_injector.wiring import Provide, inject
10
+
11
+ from diffract.core.compute import KernelConfig, KernelExecutor, KernelRegistry
12
+ from diffract.core.compute.execution import (
13
+ KernelApplyLevel,
14
+ KernelExecutionProtocol,
15
+ KernelRestrictions,
16
+ )
17
+ from diffract.core.data.nn.params.interface import IParameterRepository
18
+ from diffract.core.utils.exceptions import format_exception_message
19
+ from diffract.session.errors import KernelNotFoundError, SessionError
20
+ from diffract.session.session import Session, SessionContext
21
+
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Callable
24
+
25
+ from diffract.core.data.nn.params.schema import ParameterType
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ def _did_you_mean(name: str, candidates: list[str]) -> str:
31
+ close = difflib.get_close_matches(name, sorted(set(candidates)), n=3, cutoff=0.6)
32
+ return f" Did you mean: {', '.join(close)}?" if close else ""
33
+
34
+
35
+ class ComputeNamespace:
36
+ """Kernel compute API for Session."""
37
+
38
+ @inject
39
+ def __init__(
40
+ self,
41
+ session_or_context: Session | SessionContext,
42
+ parameter_repository: IParameterRepository = Provide["nn.parameter_repository"],
43
+ kernel_registry: KernelRegistry = Provide["compute.kernel_registry"],
44
+ kernel_executor_factory: Callable[[], KernelExecutor] = Provide[
45
+ "compute.kernel_executor.provider"
46
+ ],
47
+ ) -> None:
48
+ self.__session_or_context = session_or_context
49
+ self.__param_repo = parameter_repository
50
+ self.__kernel_registry = kernel_registry
51
+ self.__kernel_executor_factory = kernel_executor_factory
52
+
53
+ def apply(
54
+ self,
55
+ *fields_to_produce: str,
56
+ ) -> None:
57
+ """Apply computational kernels to stored parameters.
58
+
59
+ Executes the specified kernels on filtered parameters in dependency order.
60
+ Results are automatically stored and can be retrieved using export_metrics().
61
+
62
+ Args:
63
+ *fields_to_produce: Names of fields to compute using registered kernels.
64
+
65
+ Raises:
66
+ KernelNotFoundError: If any specified field cannot be produced.
67
+ SessionError: If kernel execution fails.
68
+ """
69
+ with self.__session_or_context:
70
+ if not fields_to_produce:
71
+ logger.warning("No fields specified for computation")
72
+ return
73
+
74
+ for field_name in fields_to_produce:
75
+ if not self.__kernel_registry.can_produce_field(field_name):
76
+ available = self.__kernel_registry.list_fields_can_produce()
77
+ hint = _did_you_mean(field_name, available)
78
+ msg = (
79
+ f"Cannot produce '{field_name}': no registered kernel "
80
+ f"produces it.{hint} Use "
81
+ "session.compute.list_available_metrics() to see all fields."
82
+ )
83
+ raise KernelNotFoundError(msg)
84
+
85
+ if isinstance(self.__session_or_context, Session):
86
+ pending = self.__param_repo.create_view()
87
+ else:
88
+ pending = self.__session_or_context._param_filter_context
89
+
90
+ try:
91
+ with self.__kernel_executor_factory() as executor:
92
+ for field_name in fields_to_produce:
93
+ executor.execute(
94
+ field_or_kernel_name=field_name, parameters=pending
95
+ )
96
+
97
+ logger.info(
98
+ "Successfully produced fields: %s", ", ".join(fields_to_produce)
99
+ )
100
+
101
+ self.__session_or_context._field_cache.invalidate()
102
+
103
+ except SessionError:
104
+ raise
105
+ except Exception as e:
106
+ msg = f"Failed to produce fields: {format_exception_message(e)}"
107
+ raise SessionError(msg) from e
108
+
109
+ def list_available_kernels(self, verbose: bool = False) -> list[str]:
110
+ """List all available computational kernels.
111
+
112
+ Args:
113
+ verbose: If True, include detailed kernel information including
114
+ dependencies and field requirements.
115
+
116
+ Returns:
117
+ List of kernel names or detailed kernel descriptions.
118
+ """
119
+ with self.__session_or_context:
120
+ return self.__kernel_registry.list_kernels(verbose=verbose)
121
+
122
+ def list_available_metrics(self, verbose: bool = False) -> list[str]:
123
+ """List all fields that can be computed by registered kernels.
124
+
125
+ Args:
126
+ verbose: If True, include detailed field information including
127
+ producing kernels and dependencies.
128
+
129
+ Returns:
130
+ List of computable field names or detailed field descriptions.
131
+ """
132
+ with self.__session_or_context:
133
+ return self.__kernel_registry.list_fields_can_produce(verbose=verbose)
134
+
135
+ def configure_kernel(self, kernel_name: str, **config: Any) -> None:
136
+ """Configure parameters for a specific kernel.
137
+
138
+ Updates the configuration for a registered kernel, affecting
139
+ subsequent computations using that kernel.
140
+
141
+ Args:
142
+ kernel_name: Name of kernel to configure.
143
+ **config: Kernel configuration parameters as keyword arguments.
144
+
145
+ Raises:
146
+ KernelNotFoundError: If kernel is not registered in the registry.
147
+ """
148
+ with self.__session_or_context:
149
+ if not self.__kernel_registry.has_kernel(kernel_name):
150
+ hint = _did_you_mean(kernel_name, self.__kernel_registry.list_kernels())
151
+ msg = f"Kernel '{kernel_name}' not found in registry.{hint}"
152
+ raise KernelNotFoundError(msg)
153
+
154
+ kernel_config = KernelConfig(**config)
155
+ self.__kernel_registry.configure_kernel(kernel_name, kernel_config)
156
+
157
+ logger.info("Configured kernel '%s' with: %s", kernel_name, config)
158
+
159
+ def kernel(
160
+ self,
161
+ _func: Callable[..., Any] | None = None,
162
+ *,
163
+ name: str | None = None,
164
+ require_fields: tuple[str, ...] | None = None,
165
+ produce_fields: tuple[str, ...] | None = None,
166
+ apply_level: KernelApplyLevel = KernelApplyLevel.PARAMETER,
167
+ execution_protocol: KernelExecutionProtocol = (
168
+ KernelExecutionProtocol.SEQUENTIAL
169
+ ),
170
+ restrictions: KernelRestrictions | None = None,
171
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
172
+ """Decorator for registering custom kernels in the session registry.
173
+
174
+ Creates a kernel that can compute metrics on neural network parameters.
175
+ Dependencies are automatically inferred from function arguments.
176
+
177
+ Args:
178
+ name: Custom kernel name (defaults to function name).
179
+ require_fields: Input field names (inferred from signature if None).
180
+ produce_fields: Output field names (defaults to kernel name).
181
+ apply_level: Granularity at which the kernel is applied.
182
+ execution_protocol: Execution protocol for the kernel.
183
+ restrictions: Optional restrictions applied to kernel execution.
184
+
185
+ Returns:
186
+ Decorator function for registering the kernel.
187
+
188
+ Example:
189
+ >>> session = Session()
190
+ >>> with session:
191
+ ...
192
+ ... @session.compute.kernel()
193
+ ... def my_metric(frob_norm: float, *, scale: float = 1.0) -> float:
194
+ ... return frob_norm * scale
195
+ """
196
+
197
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
198
+ req_auto, cfg = self.__kernel_registry._split_signature(func)
199
+ final_name = name or func.__name__
200
+ final_require = require_fields or req_auto
201
+ final_produce = produce_fields or (final_name,)
202
+
203
+ self.__kernel_registry.register_kernel(
204
+ name=final_name,
205
+ require_fields=final_require,
206
+ produce_fields=final_produce,
207
+ implementation=func,
208
+ apply_level=apply_level,
209
+ execution_protocol=execution_protocol,
210
+ restrictions=restrictions,
211
+ config=cfg,
212
+ info=None,
213
+ )
214
+ return func
215
+
216
+ if _func is None:
217
+ return decorator
218
+
219
+ return decorator(_func)