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,389 @@
|
|
|
1
|
+
"""Generic data view for batch operations on filtered collections.
|
|
2
|
+
|
|
3
|
+
This module provides the base DataView class that can be specialized
|
|
4
|
+
for different data types (parameters, relations, etc.).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import re
|
|
11
|
+
import types
|
|
12
|
+
from collections.abc import Iterator, Sequence
|
|
13
|
+
from typing import TYPE_CHECKING, Self, TypeVar
|
|
14
|
+
|
|
15
|
+
from tqdm.auto import tqdm
|
|
16
|
+
|
|
17
|
+
from diffract.core.constants import (
|
|
18
|
+
PROGRESS_BAR_DELAY_SEC,
|
|
19
|
+
PROGRESS_BAR_MIN_ITEMS,
|
|
20
|
+
)
|
|
21
|
+
from diffract.core.parallel import ParallelContext, map_maybe_parallel
|
|
22
|
+
from diffract.core.utils.exceptions import format_exception_message
|
|
23
|
+
|
|
24
|
+
from .interface import EntityIndex, EntityUID, FieldName
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from .interface import IDataRepository
|
|
28
|
+
|
|
29
|
+
TMetadata = TypeVar("TMetadata")
|
|
30
|
+
TProxy = TypeVar("TProxy")
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
_ERROR_PREVIEW_LIMIT = 10
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DataView[TMetadata, TProxy]:
|
|
38
|
+
"""Generic view over a subset of entities owned by a repository.
|
|
39
|
+
|
|
40
|
+
Provides numpy-like view semantics with support for filtering,
|
|
41
|
+
iteration, prefetching, and batch operations. Subclasses can add
|
|
42
|
+
domain-specific filtering methods.
|
|
43
|
+
|
|
44
|
+
Type Parameters:
|
|
45
|
+
TMetadata: The metadata type for entities in this view.
|
|
46
|
+
TProxy: The proxy type for entities in this view.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
*,
|
|
52
|
+
repository: IDataRepository[TMetadata, TProxy],
|
|
53
|
+
uids: list[EntityUID] | None = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
self._repository = repository
|
|
56
|
+
self._uids: list[EntityUID] | None = list(uids) if uids is not None else None
|
|
57
|
+
self._sorted: bool = False
|
|
58
|
+
|
|
59
|
+
def _ensure_uids(self) -> list[EntityUID]:
|
|
60
|
+
"""Ensure UIDs are loaded (lazy load from repository if None)."""
|
|
61
|
+
if self._uids is None:
|
|
62
|
+
self._uids = self._repository.list_uids()
|
|
63
|
+
self._sorted = False
|
|
64
|
+
return self._uids
|
|
65
|
+
|
|
66
|
+
def _sort_in_place(self) -> None:
|
|
67
|
+
"""Sort UIDs for deterministic ordering."""
|
|
68
|
+
uids = self._ensure_uids()
|
|
69
|
+
uids.sort()
|
|
70
|
+
self._sorted = True
|
|
71
|
+
|
|
72
|
+
def __len__(self) -> int:
|
|
73
|
+
"""Return number of entities in the view."""
|
|
74
|
+
return len(self._ensure_uids())
|
|
75
|
+
|
|
76
|
+
def __iter__(self) -> Iterator[TProxy]:
|
|
77
|
+
"""Iterate entity proxies in view order."""
|
|
78
|
+
if not self._sorted:
|
|
79
|
+
self._sort_in_place()
|
|
80
|
+
|
|
81
|
+
for uid in self._ensure_uids():
|
|
82
|
+
yield self._repository.get_proxy(uid)
|
|
83
|
+
|
|
84
|
+
def __getitem__(
|
|
85
|
+
self,
|
|
86
|
+
index: EntityIndex
|
|
87
|
+
| slice
|
|
88
|
+
| EntityUID
|
|
89
|
+
| Sequence[EntityIndex]
|
|
90
|
+
| Sequence[EntityUID],
|
|
91
|
+
) -> TProxy | Self:
|
|
92
|
+
"""Index into the view by position(s) or uid(s)."""
|
|
93
|
+
if not self._sorted:
|
|
94
|
+
self._sort_in_place()
|
|
95
|
+
|
|
96
|
+
uids = self._ensure_uids()
|
|
97
|
+
match index:
|
|
98
|
+
case int():
|
|
99
|
+
return self._repository.get_proxy(uids[index])
|
|
100
|
+
case slice():
|
|
101
|
+
return self.__class__(
|
|
102
|
+
repository=self._repository,
|
|
103
|
+
uids=uids[index],
|
|
104
|
+
)
|
|
105
|
+
case str():
|
|
106
|
+
return self._repository.get_proxy(index)
|
|
107
|
+
case _:
|
|
108
|
+
if isinstance(index, Sequence) and index:
|
|
109
|
+
first = index[0]
|
|
110
|
+
if isinstance(first, int):
|
|
111
|
+
result_uids = [uids[i] for i in index]
|
|
112
|
+
elif isinstance(first, str):
|
|
113
|
+
result_uids = list(index)
|
|
114
|
+
else:
|
|
115
|
+
msg = f"Invalid index type: {type(first)}"
|
|
116
|
+
raise ValueError(msg)
|
|
117
|
+
return self.__class__(repository=self._repository, uids=result_uids)
|
|
118
|
+
|
|
119
|
+
return self.__class__(repository=self._repository, uids=[])
|
|
120
|
+
|
|
121
|
+
def __enter__(self) -> Self:
|
|
122
|
+
"""Enter a batch write context for this view's repository storage."""
|
|
123
|
+
self._repository.storage_manager.__enter__()
|
|
124
|
+
return self
|
|
125
|
+
|
|
126
|
+
def __exit__(
|
|
127
|
+
self,
|
|
128
|
+
exc_type: type[BaseException] | None,
|
|
129
|
+
exc_val: BaseException | None,
|
|
130
|
+
exc_tb: types.TracebackType | None,
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Exit the batch write context for this view's repository storage."""
|
|
133
|
+
self._repository.storage_manager.__exit__(exc_type, exc_val, exc_tb)
|
|
134
|
+
|
|
135
|
+
def list_uids(self) -> list[EntityUID]:
|
|
136
|
+
"""Return the list of entity UIDs contained in this view."""
|
|
137
|
+
if not self._sorted:
|
|
138
|
+
self._sort_in_place()
|
|
139
|
+
return list(self._ensure_uids())
|
|
140
|
+
|
|
141
|
+
def list_fields_by_uid(
|
|
142
|
+
self,
|
|
143
|
+
*,
|
|
144
|
+
parallel: ParallelContext | None = None,
|
|
145
|
+
) -> dict[EntityUID, list[FieldName]]:
|
|
146
|
+
"""Return mapping uid -> available fields for entities in this view."""
|
|
147
|
+
if not self._sorted:
|
|
148
|
+
self._sort_in_place()
|
|
149
|
+
|
|
150
|
+
uids = self._ensure_uids()
|
|
151
|
+
if not uids:
|
|
152
|
+
return {}
|
|
153
|
+
|
|
154
|
+
def _safe_list_fields(
|
|
155
|
+
uid: EntityUID,
|
|
156
|
+
) -> tuple[EntityUID, list[FieldName], str | None]:
|
|
157
|
+
try:
|
|
158
|
+
return uid, self._repository.get_proxy(uid).list_fields(), None
|
|
159
|
+
except Exception as e: # noqa: BLE001
|
|
160
|
+
return uid, [], format_exception_message(e)
|
|
161
|
+
|
|
162
|
+
dict_items = map_maybe_parallel(
|
|
163
|
+
uids,
|
|
164
|
+
_safe_list_fields,
|
|
165
|
+
parallel=parallel,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
total = len(uids)
|
|
169
|
+
field_map: dict[EntityUID, list[FieldName]] = {}
|
|
170
|
+
failures: list[tuple[EntityUID, str]] = []
|
|
171
|
+
for uid, fields, error_type in tqdm(
|
|
172
|
+
dict_items,
|
|
173
|
+
desc="Listing fields...",
|
|
174
|
+
delay=PROGRESS_BAR_DELAY_SEC,
|
|
175
|
+
total=total,
|
|
176
|
+
disable=total < PROGRESS_BAR_MIN_ITEMS,
|
|
177
|
+
):
|
|
178
|
+
field_map[uid] = fields
|
|
179
|
+
if error_type is not None:
|
|
180
|
+
failures.append((uid, error_type))
|
|
181
|
+
|
|
182
|
+
if failures:
|
|
183
|
+
preview = ", ".join(
|
|
184
|
+
f"{uid}({err})" for uid, err in failures[:_ERROR_PREVIEW_LIMIT]
|
|
185
|
+
)
|
|
186
|
+
suffix = "..." if len(failures) > _ERROR_PREVIEW_LIMIT else ""
|
|
187
|
+
logger.warning(
|
|
188
|
+
"Failed to list fields for %d entities: %s%s",
|
|
189
|
+
len(failures),
|
|
190
|
+
preview,
|
|
191
|
+
suffix,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return field_map
|
|
195
|
+
|
|
196
|
+
def prefetch_fields(
|
|
197
|
+
self,
|
|
198
|
+
*,
|
|
199
|
+
fields_by_uid: dict[EntityUID, list[FieldName]] | None = None,
|
|
200
|
+
fields: list[FieldName] | None = None,
|
|
201
|
+
verify_prefetch: bool = False,
|
|
202
|
+
parallel: ParallelContext | None = None,
|
|
203
|
+
) -> bool:
|
|
204
|
+
"""Prefetch specified fields for entities in this view into cache."""
|
|
205
|
+
if fields_by_uid is not None and fields is not None:
|
|
206
|
+
msg = "fields_by_uid and fields are mutually exclusive."
|
|
207
|
+
raise ValueError(msg)
|
|
208
|
+
|
|
209
|
+
if fields is None and fields_by_uid is None:
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
if fields is not None and not fields:
|
|
213
|
+
return True
|
|
214
|
+
|
|
215
|
+
if fields_by_uid is not None and not fields_by_uid:
|
|
216
|
+
return True
|
|
217
|
+
|
|
218
|
+
if not self._sorted:
|
|
219
|
+
self._sort_in_place()
|
|
220
|
+
|
|
221
|
+
if fields is not None:
|
|
222
|
+
fields_by_uid = dict.fromkeys(self._ensure_uids(), fields)
|
|
223
|
+
|
|
224
|
+
if fields_by_uid is None:
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
def _prefetch_one(
|
|
228
|
+
uid: EntityUID,
|
|
229
|
+
) -> tuple[EntityUID, bool, str | None]:
|
|
230
|
+
uid_fields = fields_by_uid.get(uid) if fields_by_uid else []
|
|
231
|
+
if not uid_fields:
|
|
232
|
+
return uid, True, None
|
|
233
|
+
try:
|
|
234
|
+
proxy = self._repository.get_proxy(uid)
|
|
235
|
+
for field_name in uid_fields:
|
|
236
|
+
if not proxy.try_prefetch_field(field_name):
|
|
237
|
+
return uid, False, f"Missing field: {field_name}"
|
|
238
|
+
except Exception as e: # noqa: BLE001
|
|
239
|
+
return uid, False, format_exception_message(e)
|
|
240
|
+
else:
|
|
241
|
+
return uid, True, None
|
|
242
|
+
|
|
243
|
+
results = map_maybe_parallel(
|
|
244
|
+
list(fields_by_uid.keys()),
|
|
245
|
+
_prefetch_one,
|
|
246
|
+
parallel=parallel,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
total = len(fields_by_uid)
|
|
250
|
+
all_success = True
|
|
251
|
+
failures: list[tuple[EntityUID, str]] = []
|
|
252
|
+
|
|
253
|
+
for uid, success, error_msg in tqdm(
|
|
254
|
+
results,
|
|
255
|
+
desc="Prefetching fields...",
|
|
256
|
+
delay=PROGRESS_BAR_DELAY_SEC,
|
|
257
|
+
total=total,
|
|
258
|
+
disable=total < PROGRESS_BAR_MIN_ITEMS,
|
|
259
|
+
):
|
|
260
|
+
if not success:
|
|
261
|
+
all_success = False
|
|
262
|
+
if error_msg:
|
|
263
|
+
failures.append((uid, error_msg))
|
|
264
|
+
|
|
265
|
+
if failures:
|
|
266
|
+
preview = ", ".join(
|
|
267
|
+
f"{uid}({err})" for uid, err in failures[:_ERROR_PREVIEW_LIMIT]
|
|
268
|
+
)
|
|
269
|
+
suffix = "..." if len(failures) > _ERROR_PREVIEW_LIMIT else ""
|
|
270
|
+
logger.warning(
|
|
271
|
+
"Failed to prefetch fields for %d entities: %s%s",
|
|
272
|
+
len(failures),
|
|
273
|
+
preview,
|
|
274
|
+
suffix,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
if verify_prefetch and all_success:
|
|
278
|
+
for uid in fields_by_uid:
|
|
279
|
+
proxy = self._repository.get_proxy(uid)
|
|
280
|
+
for field_name in fields_by_uid[uid]:
|
|
281
|
+
if not proxy.is_field_prefetched(field_name):
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
return all_success
|
|
285
|
+
|
|
286
|
+
def erase_fields(self, *fields: FieldName) -> None:
|
|
287
|
+
"""Remove specified fields from entities in this view."""
|
|
288
|
+
if not fields:
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
if not self._sorted:
|
|
292
|
+
self._sort_in_place()
|
|
293
|
+
|
|
294
|
+
uids = self._ensure_uids()
|
|
295
|
+
total = len(uids)
|
|
296
|
+
for uid in tqdm(
|
|
297
|
+
uids,
|
|
298
|
+
desc="Erasing fields...",
|
|
299
|
+
delay=PROGRESS_BAR_DELAY_SEC,
|
|
300
|
+
total=total,
|
|
301
|
+
disable=total < PROGRESS_BAR_MIN_ITEMS,
|
|
302
|
+
):
|
|
303
|
+
proxy = self._repository.get_proxy(uid)
|
|
304
|
+
for field_name in fields:
|
|
305
|
+
if proxy.has_field(field_name):
|
|
306
|
+
proxy.erase_field(field_name)
|
|
307
|
+
|
|
308
|
+
def erase_fields_with_regexp(self, *patterns: str) -> None:
|
|
309
|
+
"""Remove fields matching regex patterns from entities in this view."""
|
|
310
|
+
if not patterns:
|
|
311
|
+
return
|
|
312
|
+
|
|
313
|
+
if not self._sorted:
|
|
314
|
+
self._sort_in_place()
|
|
315
|
+
|
|
316
|
+
compiled = [re.compile(p) for p in patterns]
|
|
317
|
+
|
|
318
|
+
uids = self._ensure_uids()
|
|
319
|
+
total = len(uids)
|
|
320
|
+
for uid in tqdm(
|
|
321
|
+
uids,
|
|
322
|
+
desc="Erasing fields by pattern...",
|
|
323
|
+
delay=PROGRESS_BAR_DELAY_SEC,
|
|
324
|
+
total=total,
|
|
325
|
+
disable=total < PROGRESS_BAR_MIN_ITEMS,
|
|
326
|
+
):
|
|
327
|
+
proxy = self._repository.get_proxy(uid)
|
|
328
|
+
for field_name in proxy.list_fields():
|
|
329
|
+
if any(p.fullmatch(field_name) for p in compiled):
|
|
330
|
+
proxy.erase_field(field_name)
|
|
331
|
+
|
|
332
|
+
def filter_by_fields(
|
|
333
|
+
self,
|
|
334
|
+
*fields: FieldName,
|
|
335
|
+
inverse_mask: bool = False,
|
|
336
|
+
parallel: ParallelContext | None = None,
|
|
337
|
+
) -> Self:
|
|
338
|
+
"""Filter entities by the presence of one or more fields."""
|
|
339
|
+
if not self._sorted:
|
|
340
|
+
self._sort_in_place()
|
|
341
|
+
|
|
342
|
+
uids = self._ensure_uids()
|
|
343
|
+
need_fields = tuple(fields)
|
|
344
|
+
if not need_fields:
|
|
345
|
+
return self.__class__(
|
|
346
|
+
repository=self._repository,
|
|
347
|
+
uids=uids if not inverse_mask else [],
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def _filter(uid: EntityUID) -> EntityUID | None:
|
|
351
|
+
proxy = self._repository.get_proxy(uid)
|
|
352
|
+
for field in need_fields:
|
|
353
|
+
if not proxy.has_field(field) ^ inverse_mask:
|
|
354
|
+
return None
|
|
355
|
+
return uid
|
|
356
|
+
|
|
357
|
+
filtered_uids = map_maybe_parallel(uids, _filter, parallel=parallel)
|
|
358
|
+
|
|
359
|
+
total = len(uids)
|
|
360
|
+
out = [
|
|
361
|
+
uid
|
|
362
|
+
for uid in tqdm(
|
|
363
|
+
filtered_uids,
|
|
364
|
+
desc="Filtering by fields...",
|
|
365
|
+
delay=PROGRESS_BAR_DELAY_SEC,
|
|
366
|
+
total=total,
|
|
367
|
+
disable=total < PROGRESS_BAR_MIN_ITEMS,
|
|
368
|
+
)
|
|
369
|
+
if uid is not None
|
|
370
|
+
]
|
|
371
|
+
|
|
372
|
+
return self.__class__(repository=self._repository, uids=out)
|
|
373
|
+
|
|
374
|
+
def clear(self, erase: bool = False) -> None:
|
|
375
|
+
"""Clear the view.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
erase: If True, also erase data from storage and cache.
|
|
379
|
+
"""
|
|
380
|
+
if erase:
|
|
381
|
+
for uid in self._ensure_uids():
|
|
382
|
+
proxy = self._repository.get_proxy(uid)
|
|
383
|
+
for field_name in proxy.list_fields():
|
|
384
|
+
proxy.erase_field(field_name)
|
|
385
|
+
if self._uids is not None:
|
|
386
|
+
self._uids.clear()
|
|
387
|
+
else:
|
|
388
|
+
self._uids = []
|
|
389
|
+
self._sorted = True
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Export and formatting utilities for computation results.
|
|
2
|
+
|
|
3
|
+
This package provides a unified interface to export results from parameter
|
|
4
|
+
collections into multiple output formats using pluggable formatters.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Single entry point via ResultExporter
|
|
8
|
+
- Protocols for formatters and exporters
|
|
9
|
+
- DI container wiring for default formatter selection
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
>>> from diffract.core.export import ResultExporter
|
|
13
|
+
>>> exporter = ResultExporter()
|
|
14
|
+
>>> out = exporter.export_results(
|
|
15
|
+
... "weights", parameters=collection, formatter=formatter
|
|
16
|
+
... )
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from .exporters import ResultExporter
|
|
20
|
+
from .interface import IResultExporter, IResultFormatter, StructuredExportResult
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"IResultExporter",
|
|
24
|
+
"IResultFormatter",
|
|
25
|
+
"ResultExporter",
|
|
26
|
+
"StructuredExportResult",
|
|
27
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Dependency injection container for exporting results.
|
|
2
|
+
|
|
3
|
+
Provides providers for selecting a formatter and exporting results using
|
|
4
|
+
ResultExporter. The default export format is read from config.default_export_format.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from dependency_injector import containers, providers
|
|
13
|
+
|
|
14
|
+
from .exporters import ResultExporter
|
|
15
|
+
from .formatters.registry import get_formatter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _export_with_formatter(
|
|
19
|
+
*args: Any,
|
|
20
|
+
exporter: ResultExporter,
|
|
21
|
+
export_format: str,
|
|
22
|
+
formatter_factory: Callable[[str], Any],
|
|
23
|
+
**kwargs: Any,
|
|
24
|
+
) -> Any:
|
|
25
|
+
"""Export results using dynamically selected formatter."""
|
|
26
|
+
return exporter.export_results(
|
|
27
|
+
*args,
|
|
28
|
+
formatter=formatter_factory(export_format),
|
|
29
|
+
**kwargs,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ExportContainer(containers.DeclarativeContainer):
|
|
34
|
+
"""Container for export-related dependencies."""
|
|
35
|
+
|
|
36
|
+
config = providers.Configuration()
|
|
37
|
+
|
|
38
|
+
result_formatter = providers.Factory(get_formatter)
|
|
39
|
+
|
|
40
|
+
result_exporter = providers.Singleton(ResultExporter)
|
|
41
|
+
|
|
42
|
+
export_results = providers.Factory(
|
|
43
|
+
_export_with_formatter,
|
|
44
|
+
exporter=result_exporter,
|
|
45
|
+
export_format=config.default_export_format,
|
|
46
|
+
formatter_factory=result_formatter.provider,
|
|
47
|
+
)
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Exporter implementation for collecting and formatting results.
|
|
2
|
+
|
|
3
|
+
The ResultExporter gathers requested fields from both parameter and aggregate
|
|
4
|
+
repositories, then delegates formatting to a provided IResultFormatter implementation.
|
|
5
|
+
|
|
6
|
+
Behavior:
|
|
7
|
+
- Requires at least one field name to export.
|
|
8
|
+
- Best-effort prefetch; continues if prefetch fails.
|
|
9
|
+
- Returns formatter-specific output with separate scalars and aggregates.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
from diffract.core.parallel import ParallelContext
|
|
18
|
+
from diffract.core.utils.exceptions import format_exception_message
|
|
19
|
+
|
|
20
|
+
from .interface import AggregateData, IResultExporter, IResultFormatter, ResultData
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from diffract.core.data.nn.aggregates.view import AggregateView
|
|
24
|
+
from diffract.core.data.nn.params.interface import IParameterView
|
|
25
|
+
from diffract.session.field_cache import SessionFieldCache
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SessionExportError(Exception):
|
|
31
|
+
"""Base exception for session export errors."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ResultExporter(IResultExporter):
|
|
35
|
+
"""Main implementation of result export functionality."""
|
|
36
|
+
|
|
37
|
+
def export_results(
|
|
38
|
+
self,
|
|
39
|
+
*fields: tuple[str, ...],
|
|
40
|
+
parameters: IParameterView,
|
|
41
|
+
aggregates: AggregateView | None = None,
|
|
42
|
+
formatter: IResultFormatter,
|
|
43
|
+
parallel: ParallelContext | None = None,
|
|
44
|
+
field_cache: SessionFieldCache | None = None,
|
|
45
|
+
) -> Any:
|
|
46
|
+
"""Export requested fields from parameter and aggregate repositories.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
*fields: Field names to export.
|
|
50
|
+
parameters: Parameter collection to read scalar values from.
|
|
51
|
+
aggregates: Aggregate view to read contextual/aggregation values from.
|
|
52
|
+
formatter: Formatter used to convert results to target format.
|
|
53
|
+
parallel: Optional per-method parallel context for collection operations.
|
|
54
|
+
field_cache: Optional session-level cache for field availability.
|
|
55
|
+
If provided and valid, skips expensive list_fields_by_uid() call.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Formatter-specific output object (typically StructuredExportResult).
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
ValueError: If no fields are specified.
|
|
62
|
+
"""
|
|
63
|
+
if not fields:
|
|
64
|
+
msg = "At least one field must be specified"
|
|
65
|
+
raise ValueError(msg)
|
|
66
|
+
|
|
67
|
+
param_results = self._collect_parameter_results(
|
|
68
|
+
*fields, parameters=parameters, parallel=parallel, field_cache=field_cache
|
|
69
|
+
)
|
|
70
|
+
aggregate_results = self._collect_aggregate_results(
|
|
71
|
+
*fields, aggregates=aggregates, parallel=parallel
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return formatter.format_results(param_results, aggregate_results, fields)
|
|
75
|
+
|
|
76
|
+
def _collect_parameter_results(
|
|
77
|
+
self,
|
|
78
|
+
*fields: tuple[str, ...],
|
|
79
|
+
parameters: IParameterView,
|
|
80
|
+
parallel: ParallelContext | None,
|
|
81
|
+
field_cache: SessionFieldCache | None = None,
|
|
82
|
+
) -> ResultData:
|
|
83
|
+
"""Collect results from parameter repository.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
*fields: Field names to collect (exact match only, no regex patterns).
|
|
87
|
+
parameters: Parameter collection to iterate over.
|
|
88
|
+
parallel: Optional per-method parallel context for collection operations.
|
|
89
|
+
field_cache: Optional session-level cache for field availability.
|
|
90
|
+
If provided and valid, skips expensive list_fields_by_uid() call.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Nested mapping with metadata and field values per parameter.
|
|
94
|
+
"""
|
|
95
|
+
results: ResultData = {}
|
|
96
|
+
fields_set = set(fields)
|
|
97
|
+
|
|
98
|
+
if field_cache is not None and field_cache.is_valid:
|
|
99
|
+
cached_fields_by_uid = field_cache.get()
|
|
100
|
+
required_fields = set(parameters.list_uids())
|
|
101
|
+
to_receive = tuple(set(required_fields) - set(cached_fields_by_uid.keys()))
|
|
102
|
+
received = parameters[to_receive].list_fields_by_uid(parallel=parallel)
|
|
103
|
+
field_cache.update(received)
|
|
104
|
+
cached_fields_by_uid = field_cache.get()
|
|
105
|
+
fields_by_uid = {uid: cached_fields_by_uid[uid] for uid in required_fields}
|
|
106
|
+
else:
|
|
107
|
+
fields_by_uid = parameters.list_fields_by_uid(parallel=parallel)
|
|
108
|
+
if field_cache is not None:
|
|
109
|
+
field_cache.set(fields_by_uid)
|
|
110
|
+
|
|
111
|
+
to_receive: dict[str, list[str]] = {}
|
|
112
|
+
for uid, fields_available in fields_by_uid.items():
|
|
113
|
+
to_receive[uid] = [f for f in fields_available if f in fields_set]
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
parameters.prefetch_fields(fields_by_uid=to_receive, parallel=parallel)
|
|
117
|
+
except Exception as e: # noqa: BLE001
|
|
118
|
+
logger.debug(
|
|
119
|
+
"Prefetch failed during export: %s",
|
|
120
|
+
format_exception_message(e),
|
|
121
|
+
exc_info=True,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
for param in parameters:
|
|
125
|
+
param_results: dict[str, Any] = {}
|
|
126
|
+
for field in to_receive[param.meta.uid]:
|
|
127
|
+
param_results[field] = param.get_field(field, auto_prefetch=False)
|
|
128
|
+
|
|
129
|
+
results[param.meta.uid] = {
|
|
130
|
+
"metadata": {
|
|
131
|
+
"name": param.meta.name,
|
|
132
|
+
"model_id": param.meta.model_id,
|
|
133
|
+
"parameter_type": param.meta.ptype.name,
|
|
134
|
+
**param.meta.other_meta,
|
|
135
|
+
},
|
|
136
|
+
"fields": param_results,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return results
|
|
140
|
+
|
|
141
|
+
def _collect_aggregate_results(
|
|
142
|
+
self,
|
|
143
|
+
*fields: tuple[str, ...],
|
|
144
|
+
aggregates: AggregateView | None,
|
|
145
|
+
parallel: ParallelContext | None,
|
|
146
|
+
) -> AggregateData:
|
|
147
|
+
"""Collect results from aggregate repository.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
*fields: Base field names to collect.
|
|
151
|
+
aggregates: Aggregate view to iterate over.
|
|
152
|
+
parallel: Optional per-method parallel context (unused currently).
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
List of aggregate records with field name, context, and value.
|
|
156
|
+
"""
|
|
157
|
+
if aggregates is None or not aggregates:
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
fields_set = set(fields)
|
|
161
|
+
results: AggregateData = []
|
|
162
|
+
|
|
163
|
+
# Filter aggregates by requested field names
|
|
164
|
+
filtered_view = aggregates.filter_by_field_name(*fields)
|
|
165
|
+
|
|
166
|
+
# Prefetch values
|
|
167
|
+
try:
|
|
168
|
+
filtered_view.prefetch_fields(fields=["value"], parallel=parallel)
|
|
169
|
+
except Exception as e: # noqa: BLE001
|
|
170
|
+
logger.debug(
|
|
171
|
+
"Prefetch failed during aggregate export: %s",
|
|
172
|
+
format_exception_message(e),
|
|
173
|
+
exc_info=True,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
for aggregate in filtered_view:
|
|
177
|
+
if aggregate.meta.field_name not in fields_set:
|
|
178
|
+
continue
|
|
179
|
+
if not aggregate.has_field("value"):
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
results.append(
|
|
183
|
+
{
|
|
184
|
+
"field": aggregate.meta.field_name,
|
|
185
|
+
"context_models": aggregate.meta.context_models,
|
|
186
|
+
"context_params": aggregate.meta.context_params,
|
|
187
|
+
"value": aggregate.get_field("value"),
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return results
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Formatters for exporting results to different output formats.
|
|
2
|
+
|
|
3
|
+
Includes JSON, dictionary, and optional pandas/polars DataFrame formatters along
|
|
4
|
+
with a small registry/factory for runtime selection by name.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .dict_formatter import DictFormatter
|
|
8
|
+
from .json_formatter import JsonFormatter
|
|
9
|
+
from .list_formatter import ListFormatter
|
|
10
|
+
from .pandas_formatter import PandasFormatter # type: ignore[F401]
|
|
11
|
+
from .polars_formatter import PolarsFormatter # type: ignore[F401]
|
|
12
|
+
from .registry import FORMATTERS, get_formatter, register_formatter
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"FORMATTERS",
|
|
16
|
+
"DictFormatter",
|
|
17
|
+
"JsonFormatter",
|
|
18
|
+
"ListFormatter",
|
|
19
|
+
"PandasFormatter",
|
|
20
|
+
"PolarsFormatter",
|
|
21
|
+
"get_formatter",
|
|
22
|
+
"register_formatter",
|
|
23
|
+
]
|