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,378 @@
|
|
|
1
|
+
"""Results namespace for Session."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from dependency_injector.wiring import Provide, inject
|
|
11
|
+
|
|
12
|
+
from diffract.core.data.nn.params.interface import IParameterRepository
|
|
13
|
+
from diffract.core.export.formatters.registry import get_formatter
|
|
14
|
+
from diffract.core.parallel import ParallelContext
|
|
15
|
+
from diffract.session.errors import KernelNotFoundError, SessionError
|
|
16
|
+
from diffract.session.namespaces.results.eraser import ResultsEraser, ResultsEraserError
|
|
17
|
+
from diffract.session.namespaces.results.injester import (
|
|
18
|
+
AggregateIngester,
|
|
19
|
+
AggregateIngestionError,
|
|
20
|
+
FieldIngester,
|
|
21
|
+
FieldIngestionError,
|
|
22
|
+
)
|
|
23
|
+
from diffract.session.session import Session, SessionContext
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from diffract.core.compute.registry import KernelRegistry
|
|
27
|
+
from diffract.core.data.nn.aggregates.repository import AggregateRepository
|
|
28
|
+
from diffract.core.data.nn.aggregates.view import AggregateView
|
|
29
|
+
from diffract.core.data.nn.params.interface import IParameterView
|
|
30
|
+
from diffract.core.export.exporters import ResultExporter
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ResultsNamespace:
|
|
36
|
+
"""Results export and ingestion 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
|
+
aggregate_repository: AggregateRepository = Provide["nn.aggregate_repository"],
|
|
44
|
+
kernel_registry: KernelRegistry = Provide["compute.kernel_registry"],
|
|
45
|
+
parallel_context_factory: Callable[[], ParallelContext] = Provide[
|
|
46
|
+
"parallel_singleton.thread_pool_context.provider"
|
|
47
|
+
],
|
|
48
|
+
results_exporter_factory: Callable[
|
|
49
|
+
[tuple[str, ...], IParameterView, str], Any
|
|
50
|
+
] = Provide["export.result_exporter.provider"],
|
|
51
|
+
) -> None:
|
|
52
|
+
self.__session_or_context = session_or_context
|
|
53
|
+
self.__param_repo = parameter_repository
|
|
54
|
+
self.__agg_repo = aggregate_repository
|
|
55
|
+
self.__kernel_registry = kernel_registry
|
|
56
|
+
self.__parallel_context_factory = parallel_context_factory
|
|
57
|
+
self.__results_exporter_factory = results_exporter_factory
|
|
58
|
+
|
|
59
|
+
def export_metrics(
|
|
60
|
+
self,
|
|
61
|
+
*fields: str,
|
|
62
|
+
export_format: str = "dict",
|
|
63
|
+
) -> Any:
|
|
64
|
+
"""Retrieve computation results for specified fields (metrics only).
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
*fields: Field names to retrieve.
|
|
68
|
+
export_format: Output format - "dict", "json", "pandas", "polars", "list".
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Metric results in the specified format.
|
|
72
|
+
"""
|
|
73
|
+
with self.__session_or_context:
|
|
74
|
+
if not fields:
|
|
75
|
+
raise ValueError("At least one field must be specified")
|
|
76
|
+
|
|
77
|
+
if isinstance(self.__session_or_context, Session):
|
|
78
|
+
params = self.__param_repo.create_view()
|
|
79
|
+
else:
|
|
80
|
+
params = self.__session_or_context._param_filter_context
|
|
81
|
+
|
|
82
|
+
exporter: ResultExporter = self.__results_exporter_factory()
|
|
83
|
+
param_results = exporter._collect_parameter_results(
|
|
84
|
+
*fields,
|
|
85
|
+
parameters=params,
|
|
86
|
+
parallel=self.__parallel_context_factory(),
|
|
87
|
+
field_cache=self.__session_or_context._field_cache,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if export_format == "json":
|
|
91
|
+
return self._dump_json(param_results)
|
|
92
|
+
|
|
93
|
+
formatter = get_formatter(export_format)
|
|
94
|
+
formatted = formatter.format_results(param_results, [], fields)
|
|
95
|
+
return formatted.scalars
|
|
96
|
+
|
|
97
|
+
def export_aggregates(
|
|
98
|
+
self,
|
|
99
|
+
*fields: str,
|
|
100
|
+
export_format: str = "dict",
|
|
101
|
+
) -> Any:
|
|
102
|
+
"""Retrieve aggregation results for specified fields (aggregates only).
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
*fields: Field names to retrieve.
|
|
106
|
+
export_format: Output format - "dict", "json", "pandas", "polars", "list".
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Aggregation results in the specified format.
|
|
110
|
+
"""
|
|
111
|
+
with self.__session_or_context:
|
|
112
|
+
if not fields:
|
|
113
|
+
raise ValueError("At least one field must be specified")
|
|
114
|
+
|
|
115
|
+
if isinstance(self.__session_or_context, Session):
|
|
116
|
+
aggs = self.__agg_repo.create_view()
|
|
117
|
+
else:
|
|
118
|
+
aggs = self.__session_or_context._agg_filter_context
|
|
119
|
+
|
|
120
|
+
exporter: ResultExporter = self.__results_exporter_factory()
|
|
121
|
+
aggregate_results = exporter._collect_aggregate_results(
|
|
122
|
+
*fields,
|
|
123
|
+
aggregates=aggs,
|
|
124
|
+
parallel=self.__parallel_context_factory(),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if export_format == "json":
|
|
128
|
+
return self._dump_json(aggregate_results)
|
|
129
|
+
|
|
130
|
+
formatter = get_formatter(export_format)
|
|
131
|
+
formatted = formatter.format_results({}, aggregate_results, fields)
|
|
132
|
+
return formatted.aggregates
|
|
133
|
+
|
|
134
|
+
def ingest_metrics(
|
|
135
|
+
self,
|
|
136
|
+
fields_by_uid: dict[str, dict[str, Any]],
|
|
137
|
+
*,
|
|
138
|
+
force: bool = False,
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Ingest precomputed fields into the session via a uid->field mapping.
|
|
141
|
+
|
|
142
|
+
Field names are stored exactly as provided (including contextual suffixes
|
|
143
|
+
like "metric@ctx"). By default, this method is strict and raises if any
|
|
144
|
+
target field already exists; set force=True to overwrite.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
fields_by_uid: Mapping of parameter uid -> {field_name: value}.
|
|
148
|
+
force: If False (default), raise on any existing field conflict.
|
|
149
|
+
If True, overwrite existing field values.
|
|
150
|
+
|
|
151
|
+
Raises:
|
|
152
|
+
SessionError: If unknown uids are provided or conflicts are
|
|
153
|
+
detected (force=False).
|
|
154
|
+
"""
|
|
155
|
+
with self.__session_or_context:
|
|
156
|
+
if not fields_by_uid:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
params = self.__param_repo.create_view()
|
|
160
|
+
filtered_params = params[fields_by_uid.keys()]
|
|
161
|
+
|
|
162
|
+
ingester = FieldIngester()
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
ingester.ingest(
|
|
166
|
+
fields_by_uid=fields_by_uid,
|
|
167
|
+
parameters=filtered_params,
|
|
168
|
+
force=force,
|
|
169
|
+
)
|
|
170
|
+
except FieldIngestionError as e:
|
|
171
|
+
raise SessionError(str(e)) from e
|
|
172
|
+
|
|
173
|
+
all_fields: set[str] = set()
|
|
174
|
+
for field_dict in fields_by_uid.values():
|
|
175
|
+
all_fields.update(field_dict.keys())
|
|
176
|
+
|
|
177
|
+
self.__session_or_context._field_cache.add_computed_fields(
|
|
178
|
+
affected_uids=fields_by_uid.keys(),
|
|
179
|
+
new_fields=all_fields,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def ingest_aggregates(
|
|
183
|
+
self,
|
|
184
|
+
aggregates: list[dict[str, Any]],
|
|
185
|
+
*,
|
|
186
|
+
force: bool = False,
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Ingest precomputed aggregate values into the session.
|
|
189
|
+
|
|
190
|
+
Each aggregate is identified by (field_name, context_models, context_params).
|
|
191
|
+
Useful for importing precomputed aggregation results.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
aggregates: List of aggregate dictionaries, each containing:
|
|
195
|
+
- field_name: Base field name (e.g., "l_overlap").
|
|
196
|
+
- context_models: Tuple/list of model IDs.
|
|
197
|
+
- context_params: Tuple/list of parameter names (optional).
|
|
198
|
+
- value: The computed value to store.
|
|
199
|
+
force: If False (default), raise on existing aggregate conflict.
|
|
200
|
+
If True, overwrite existing values.
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
SessionError: If aggregate structure is invalid or conflicts are
|
|
204
|
+
detected (force=False).
|
|
205
|
+
"""
|
|
206
|
+
with self.__session_or_context:
|
|
207
|
+
if not aggregates:
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
ingester = AggregateIngester()
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
ingester.ingest(
|
|
214
|
+
aggregates=aggregates,
|
|
215
|
+
repository=self.__agg_repo,
|
|
216
|
+
force=force,
|
|
217
|
+
)
|
|
218
|
+
except AggregateIngestionError as e:
|
|
219
|
+
raise SessionError(str(e)) from e
|
|
220
|
+
|
|
221
|
+
def erase(
|
|
222
|
+
self,
|
|
223
|
+
*fields_to_erase: str,
|
|
224
|
+
erase_dependent_also: bool = False,
|
|
225
|
+
erase_all: bool = False,
|
|
226
|
+
) -> None:
|
|
227
|
+
"""Erase computation results for specified fields.
|
|
228
|
+
|
|
229
|
+
Removes computed field data from parameters while preserving the
|
|
230
|
+
parameters themselves. Optionally erases dependent fields that
|
|
231
|
+
rely on the specified fields as inputs.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
*fields_to_erase: Field names to erase. Required if erase_all is False.
|
|
235
|
+
erase_dependent_also: If True, also erases fields that depend on
|
|
236
|
+
the specified fields.
|
|
237
|
+
erase_all: If True, erases all computed fields. fields_to_erase
|
|
238
|
+
must be empty.
|
|
239
|
+
|
|
240
|
+
Raises:
|
|
241
|
+
ValueError: If neither fields_to_erase nor erase_all is specified,
|
|
242
|
+
or if both are specified.
|
|
243
|
+
KernelNotFoundError: If any specified field cannot be produced
|
|
244
|
+
by registered kernels.
|
|
245
|
+
"""
|
|
246
|
+
with self.__session_or_context:
|
|
247
|
+
if (not fields_to_erase) ^ erase_all:
|
|
248
|
+
if not fields_to_erase:
|
|
249
|
+
msg = "No fields_to_erase provided and erase_all=False"
|
|
250
|
+
else:
|
|
251
|
+
msg = "Cannot specify both fields_to_erase and erase_all=True"
|
|
252
|
+
raise ValueError(msg)
|
|
253
|
+
|
|
254
|
+
eraser = ResultsEraser(kernel_registry=self.__kernel_registry)
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
if erase_all:
|
|
258
|
+
fields = set(self.__kernel_registry.list_fields_can_produce())
|
|
259
|
+
else:
|
|
260
|
+
fields = eraser.resolve_fields_to_erase(
|
|
261
|
+
fields_to_erase, erase_dependent_also
|
|
262
|
+
)
|
|
263
|
+
except ResultsEraserError as e:
|
|
264
|
+
raise KernelNotFoundError(str(e)) from e
|
|
265
|
+
|
|
266
|
+
if isinstance(self.__session_or_context, Session):
|
|
267
|
+
params = self.__param_repo.create_view()
|
|
268
|
+
aggs = self.__agg_repo.create_view()
|
|
269
|
+
else:
|
|
270
|
+
params = self.__session_or_context._param_filter_context
|
|
271
|
+
aggs = self.__session_or_context._agg_filter_context
|
|
272
|
+
|
|
273
|
+
eraser.erase(parameters=params, aggregates=aggs, fields=fields)
|
|
274
|
+
|
|
275
|
+
self.__session_or_context._field_cache.remove_fields_by_uids(
|
|
276
|
+
params.list_uids(), fields
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def export(
|
|
280
|
+
self,
|
|
281
|
+
*fields: str,
|
|
282
|
+
sources: str = "all",
|
|
283
|
+
export_format: str = "dict",
|
|
284
|
+
expand_contextual: bool = True,
|
|
285
|
+
) -> Any:
|
|
286
|
+
"""Unified export of metrics and aggregates.
|
|
287
|
+
|
|
288
|
+
This method provides a single interface for exporting both parameter
|
|
289
|
+
metrics and aggregate values. When both sources are requested,
|
|
290
|
+
aggregate values are merged into parameter entries as contextual fields.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
*fields: Field names to retrieve.
|
|
294
|
+
sources: Data sources to include - "metrics", "aggregates", or "all".
|
|
295
|
+
export_format: Output format - "dict", "json", "pandas", "polars", "list".
|
|
296
|
+
expand_contextual: When True and sources="all", merge aggregate values
|
|
297
|
+
into parameter entries as contextual field names.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Results in the specified format.
|
|
301
|
+
"""
|
|
302
|
+
if sources not in ("metrics", "aggregates", "all"):
|
|
303
|
+
raise ValueError(
|
|
304
|
+
f"Invalid sources: {sources}. Use 'metrics', 'aggregates', or 'all'."
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if sources == "metrics":
|
|
308
|
+
return self.export_metrics(*fields, export_format=export_format)
|
|
309
|
+
|
|
310
|
+
if sources == "aggregates":
|
|
311
|
+
return self.export_aggregates(*fields, export_format=export_format)
|
|
312
|
+
|
|
313
|
+
metrics = self.export_metrics(*fields, export_format="dict")
|
|
314
|
+
aggregates = self.export_aggregates(*fields, export_format="dict")
|
|
315
|
+
|
|
316
|
+
if expand_contextual and aggregates:
|
|
317
|
+
self._merge_aggregates_into_metrics(metrics, aggregates)
|
|
318
|
+
|
|
319
|
+
if export_format == "dict":
|
|
320
|
+
return metrics
|
|
321
|
+
|
|
322
|
+
if export_format == "json":
|
|
323
|
+
return self._dump_json(metrics)
|
|
324
|
+
|
|
325
|
+
formatter = get_formatter(export_format)
|
|
326
|
+
formatted = formatter.format_results(metrics, [], fields)
|
|
327
|
+
return formatted.scalars
|
|
328
|
+
|
|
329
|
+
def _merge_aggregates_into_metrics(
|
|
330
|
+
self,
|
|
331
|
+
metrics: dict[str, Any],
|
|
332
|
+
aggregates: list[dict[str, Any]],
|
|
333
|
+
) -> None:
|
|
334
|
+
"""Merge aggregate values into parameter entries as contextual fields."""
|
|
335
|
+
from diffract.core.constants import format_contextual_field_name
|
|
336
|
+
|
|
337
|
+
for agg in aggregates:
|
|
338
|
+
self._merge_single_aggregate(metrics, agg, format_contextual_field_name)
|
|
339
|
+
|
|
340
|
+
@staticmethod
|
|
341
|
+
def _merge_single_aggregate(
|
|
342
|
+
metrics: dict[str, Any],
|
|
343
|
+
agg: dict[str, Any],
|
|
344
|
+
format_fn: Any,
|
|
345
|
+
) -> None:
|
|
346
|
+
"""Merge a single aggregate into matching parameter entries."""
|
|
347
|
+
context_params = agg.get("context_params", ())
|
|
348
|
+
context_models = agg.get("context_models", ())
|
|
349
|
+
|
|
350
|
+
value = agg.get("value")
|
|
351
|
+
if not context_params or value is None:
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
contextual_name = format_fn(
|
|
355
|
+
agg.get("field"),
|
|
356
|
+
models=context_models,
|
|
357
|
+
params=context_params,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
for uid, entry in metrics.items():
|
|
361
|
+
meta = entry.get("metadata", {})
|
|
362
|
+
param_name = meta.get("name")
|
|
363
|
+
model_id = meta.get("model_id")
|
|
364
|
+
|
|
365
|
+
if not param_name or not model_id:
|
|
366
|
+
continue
|
|
367
|
+
|
|
368
|
+
if param_name in context_params and model_id in context_models:
|
|
369
|
+
metrics[uid].setdefault("fields", {})[contextual_name] = value
|
|
370
|
+
|
|
371
|
+
@staticmethod
|
|
372
|
+
def _dump_json(data: Any) -> str:
|
|
373
|
+
def _json_serializer(obj: object) -> object:
|
|
374
|
+
if hasattr(obj, "tolist"):
|
|
375
|
+
return obj.tolist()
|
|
376
|
+
return str(obj)
|
|
377
|
+
|
|
378
|
+
return json.dumps(data, indent=2, default=_json_serializer)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Results namespace for Session."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
from collections.abc import Iterable
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from diffract.core.compute.registry import KernelRegistry
|
|
12
|
+
from diffract.core.data.nn.aggregates.view import AggregateView
|
|
13
|
+
from diffract.core.data.nn.params.interface import IParameterView
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
_ERROR_PREVIEW_LIMIT = 10
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ResultsEraserError(Exception):
|
|
21
|
+
"""Error during results erasure."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ResultsEraser:
|
|
25
|
+
"""Utility for erasing computed results with dependency resolution.
|
|
26
|
+
|
|
27
|
+
Handles kernel dependency resolution to determine the full set of
|
|
28
|
+
fields to erase.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
>>> eraser = ResultsEraser(kernel_registry=registry)
|
|
32
|
+
>>> fields = eraser.resolve_fields_to_erase(
|
|
33
|
+
... ["frob_norm"], erase_dependent_also=True
|
|
34
|
+
... )
|
|
35
|
+
>>> eraser.erase(view=session._get_view(), fields=fields)
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, kernel_registry: KernelRegistry) -> None:
|
|
39
|
+
"""Initialize the eraser.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
kernel_registry: Registry for kernel dependency resolution.
|
|
43
|
+
"""
|
|
44
|
+
self._registry = kernel_registry
|
|
45
|
+
|
|
46
|
+
def resolve_fields_to_erase(
|
|
47
|
+
self,
|
|
48
|
+
fields_to_erase: Iterable[str],
|
|
49
|
+
erase_dependent_also: bool,
|
|
50
|
+
) -> set[str]:
|
|
51
|
+
"""Resolve kernel dependencies to get full set of fields to erase.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
fields_to_erase: Initial fields to erase.
|
|
55
|
+
erase_dependent_also: If True, include fields that depend
|
|
56
|
+
on the specified fields.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Complete set of fields to erase.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
ResultsEraserError: If a field cannot be produced by any kernel.
|
|
63
|
+
"""
|
|
64
|
+
result: list[str] = []
|
|
65
|
+
|
|
66
|
+
for field_name in fields_to_erase:
|
|
67
|
+
if not self._registry.can_produce_field(field_name):
|
|
68
|
+
msg = f"Registry cannot produce '{field_name}'"
|
|
69
|
+
raise ResultsEraserError(msg)
|
|
70
|
+
|
|
71
|
+
result.append(field_name)
|
|
72
|
+
|
|
73
|
+
if erase_dependent_also:
|
|
74
|
+
result.extend(self._find_dependent_fields(field_name))
|
|
75
|
+
|
|
76
|
+
return set(result)
|
|
77
|
+
|
|
78
|
+
def _find_dependent_fields(self, field_name: str) -> list[str]:
|
|
79
|
+
"""Find all fields that depend on the given field."""
|
|
80
|
+
dependent_fields: list[str] = []
|
|
81
|
+
|
|
82
|
+
producer = self._registry.get_kernel_producing_field(field_name)
|
|
83
|
+
all_kernels = self._registry.list_kernels()
|
|
84
|
+
|
|
85
|
+
for other_kernel in all_kernels:
|
|
86
|
+
if other_kernel == producer:
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
dependencies = self._registry.resolve_dependencies(other_kernel)
|
|
90
|
+
fields_dependencies: list[str] = []
|
|
91
|
+
|
|
92
|
+
for dependency in dependencies:
|
|
93
|
+
fields_dependencies.extend(
|
|
94
|
+
self._registry.get_fields_kernel_require(dependency)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if field_name in fields_dependencies:
|
|
98
|
+
dependent_fields.extend(
|
|
99
|
+
self._registry.get_fields_kernel_produce(other_kernel)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return dependent_fields
|
|
103
|
+
|
|
104
|
+
def erase(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
parameters: IParameterView,
|
|
108
|
+
aggregates: AggregateView | None = None,
|
|
109
|
+
fields: set[str],
|
|
110
|
+
) -> None:
|
|
111
|
+
"""Erase fields from parameters and aggregates.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
parameters: Parameter view to erase scalar fields from.
|
|
115
|
+
aggregates: Aggregate view to erase aggregate entries from.
|
|
116
|
+
fields: Set of base field names to erase.
|
|
117
|
+
"""
|
|
118
|
+
logger.debug("Erasing fields: %s", ", ".join(sorted(fields)))
|
|
119
|
+
|
|
120
|
+
# Erase scalar fields from parameters
|
|
121
|
+
re_patterns = [rf"{re.escape(f)}" for f in fields]
|
|
122
|
+
parameters.erase_fields_with_regexp(*re_patterns)
|
|
123
|
+
|
|
124
|
+
# Erase aggregate entries by field_name
|
|
125
|
+
if aggregates is not None:
|
|
126
|
+
aggregates_to_erase = aggregates.filter_by_field_name(*fields)
|
|
127
|
+
if aggregates_to_erase:
|
|
128
|
+
logger.debug("Erasing %d aggregate entries", len(aggregates_to_erase))
|
|
129
|
+
aggregates_to_erase.clear(erase=True)
|