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,260 @@
|
|
|
1
|
+
"""Main Session API for the diffract package.
|
|
2
|
+
|
|
3
|
+
This module implements the primary user interface for neural network parameter
|
|
4
|
+
analysis workflows, providing a high-level API for model management, kernel
|
|
5
|
+
execution, and result retrieval through dependency injection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import types
|
|
12
|
+
from contextlib import nullcontext
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Self
|
|
15
|
+
|
|
16
|
+
from dependency_injector.wiring import Provide, inject
|
|
17
|
+
|
|
18
|
+
from diffract.containers import MainContainer, create_main_container
|
|
19
|
+
from diffract.core.data.nn.aggregates.repository import AggregateRepository
|
|
20
|
+
from diffract.core.data.nn.aggregates.view import AggregateView
|
|
21
|
+
from diffract.core.data.nn.params.interface import IParameterRepository, IParameterView
|
|
22
|
+
from diffract.core.data.nn.params.schema import ParameterType
|
|
23
|
+
from diffract.session.field_cache import SessionFieldCache
|
|
24
|
+
from diffract.session.utils import filter_aggregate_view, filter_parameter_view
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Session:
|
|
30
|
+
"""Main session class for neural network parameter analysis.
|
|
31
|
+
|
|
32
|
+
Provides a high-level interface for adding models, applying computational
|
|
33
|
+
kernels, and retrieving results. Manages the complete lifecycle of parameter
|
|
34
|
+
analysis workflows through dependency injection and modular components.
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
>>> session = Session()
|
|
38
|
+
>>> session.models.add(my_model, model_id="bert-base")
|
|
39
|
+
>>> session.compute.apply("frob_norm", "stable_rank")
|
|
40
|
+
>>> results = session.results.export_metrics(
|
|
41
|
+
... "frob_norm", "stable_rank", export_format="pandas"
|
|
42
|
+
... )
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
profile: str | None = "ram",
|
|
48
|
+
config_path: str | Path | None = None,
|
|
49
|
+
container: MainContainer | None = None,
|
|
50
|
+
other_session: Session | None = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Initialize a new session.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
profile: Built-in profile name for quick setup:
|
|
56
|
+
- "ram": RAM storage, no persistence (fast experiments)
|
|
57
|
+
- "local": SQLite storage in .diffract/ (persistent, simple)
|
|
58
|
+
- "hybrid": SQLite + HDF5 (persistent, optimized for large arrays)
|
|
59
|
+
config_path: Path to configuration file (YAML/JSON/INI). Takes
|
|
60
|
+
priority over profile if both are provided.
|
|
61
|
+
container: Pre-configured MainContainer instance. If None,
|
|
62
|
+
creates new container using config_path or profile.
|
|
63
|
+
other_session: Another Session instance to copy configuration from.
|
|
64
|
+
"""
|
|
65
|
+
from diffract.session.namespaces import (
|
|
66
|
+
ComputeNamespace,
|
|
67
|
+
ModelsNamespace,
|
|
68
|
+
ResultsNamespace,
|
|
69
|
+
UtilsNamespace,
|
|
70
|
+
VizNamespace,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# For namespaces
|
|
74
|
+
self._field_cache = SessionFieldCache()
|
|
75
|
+
|
|
76
|
+
if container is None:
|
|
77
|
+
self.__container = create_main_container(config_path, profile=profile)
|
|
78
|
+
else:
|
|
79
|
+
self.__container = container
|
|
80
|
+
|
|
81
|
+
self.__context_depth = 0
|
|
82
|
+
|
|
83
|
+
with self:
|
|
84
|
+
logger.debug("Loading parameters...")
|
|
85
|
+
self.__init_repos()
|
|
86
|
+
logger.info(
|
|
87
|
+
"Session initialized with %d existing parameters, %d aggregates",
|
|
88
|
+
len(self.__param_repo),
|
|
89
|
+
len(self.__agg_repo),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
self.models = ModelsNamespace(self)
|
|
93
|
+
self.compute = ComputeNamespace(self)
|
|
94
|
+
self.results = ResultsNamespace(self)
|
|
95
|
+
self.viz = VizNamespace(self)
|
|
96
|
+
self.utils = UtilsNamespace(self)
|
|
97
|
+
|
|
98
|
+
if other_session is not None:
|
|
99
|
+
self.utils.merge_other_session(other_session, verify=False)
|
|
100
|
+
|
|
101
|
+
def __enter__(self) -> Self | nullcontext[Self]:
|
|
102
|
+
"""Enter session context and initialize resources."""
|
|
103
|
+
self.__context_depth += 1
|
|
104
|
+
if self.__context_depth == 1:
|
|
105
|
+
logger.debug("Init resources called")
|
|
106
|
+
self.__container.init_resources()
|
|
107
|
+
return self
|
|
108
|
+
return nullcontext()
|
|
109
|
+
|
|
110
|
+
def __exit__(
|
|
111
|
+
self,
|
|
112
|
+
exc_type: type[BaseException] | None,
|
|
113
|
+
exc_val: BaseException | None,
|
|
114
|
+
exc_tb: types.TracebackType | None,
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Exit session context and shutdown resources."""
|
|
117
|
+
self.__context_depth -= 1
|
|
118
|
+
if self.__context_depth == 0:
|
|
119
|
+
logger.debug("Shutdown resources called")
|
|
120
|
+
self.__container.shutdown_resources()
|
|
121
|
+
|
|
122
|
+
@inject
|
|
123
|
+
def __init_repos(
|
|
124
|
+
self,
|
|
125
|
+
parameter_repository: IParameterRepository = Provide["nn.parameter_repository"],
|
|
126
|
+
aggregate_repository: AggregateRepository = Provide["nn.aggregate_repository"],
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Inject dependencies from the container."""
|
|
129
|
+
self.__param_repo = parameter_repository
|
|
130
|
+
self.__agg_repo = aggregate_repository
|
|
131
|
+
|
|
132
|
+
def filter(
|
|
133
|
+
self,
|
|
134
|
+
param_ids: list[str] | None = None,
|
|
135
|
+
param_names: list[str] | None = None,
|
|
136
|
+
param_types: list[ParameterType] | None = None,
|
|
137
|
+
model_ids: list[str] | None = None,
|
|
138
|
+
) -> SessionContext:
|
|
139
|
+
"""Create a filtered view of the session's parameters and aggregates.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
param_ids: Parameter IDs to keep, or None for no ID filtering.
|
|
143
|
+
param_names: Parameter names to keep, or None for no name filtering.
|
|
144
|
+
param_types: Parameter types to keep, or None for no type filtering.
|
|
145
|
+
model_ids: Model IDs to keep, or None for no model filtering.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
A SessionContext scoped to the filtered parameter and aggregate views.
|
|
149
|
+
"""
|
|
150
|
+
with self:
|
|
151
|
+
param_view = self.__param_repo.create_view()
|
|
152
|
+
filtered_param_view = filter_parameter_view(
|
|
153
|
+
param_view,
|
|
154
|
+
parameter_ids=param_ids,
|
|
155
|
+
parameter_names=param_names,
|
|
156
|
+
parameter_types=param_types,
|
|
157
|
+
model_ids=model_ids,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
agg_view = self.__agg_repo.create_view()
|
|
161
|
+
filtered_agg_view = filter_aggregate_view(
|
|
162
|
+
agg_view,
|
|
163
|
+
parameter_names=param_names,
|
|
164
|
+
model_ids=model_ids,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return SessionContext(self, filtered_param_view, filtered_agg_view)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class SessionContext:
|
|
171
|
+
"""Filtered, scoped view over a session's parameters and aggregates.
|
|
172
|
+
|
|
173
|
+
Wraps a parent Session together with pre-filtered parameter and aggregate
|
|
174
|
+
views, exposing the same namespaces (models, compute, results, viz, utils)
|
|
175
|
+
restricted to the filtered scope. Supports further chained filtering.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
def __init__(
|
|
179
|
+
self,
|
|
180
|
+
session: Session,
|
|
181
|
+
param_filter_context: IParameterView,
|
|
182
|
+
agg_filter_context: AggregateView,
|
|
183
|
+
) -> None:
|
|
184
|
+
"""Initialize a filtered session context.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
session: The parent Session this context is derived from.
|
|
188
|
+
param_filter_context: Pre-filtered parameter view for this scope.
|
|
189
|
+
agg_filter_context: Pre-filtered aggregate view for this scope.
|
|
190
|
+
"""
|
|
191
|
+
from diffract.session.namespaces import (
|
|
192
|
+
ComputeNamespace,
|
|
193
|
+
ModelsNamespace,
|
|
194
|
+
ResultsNamespace,
|
|
195
|
+
UtilsNamespace,
|
|
196
|
+
VizNamespace,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
self.__session = session
|
|
200
|
+
|
|
201
|
+
# For namespaces
|
|
202
|
+
self._param_filter_context = param_filter_context
|
|
203
|
+
self._agg_filter_context = agg_filter_context
|
|
204
|
+
self._field_cache = session._field_cache
|
|
205
|
+
|
|
206
|
+
self.models = ModelsNamespace(self)
|
|
207
|
+
self.compute = ComputeNamespace(self)
|
|
208
|
+
self.results = ResultsNamespace(self)
|
|
209
|
+
self.viz = VizNamespace(self)
|
|
210
|
+
self.utils = UtilsNamespace(self)
|
|
211
|
+
|
|
212
|
+
def __enter__(self) -> Self | nullcontext[Self]:
|
|
213
|
+
"""Enter the underlying session context."""
|
|
214
|
+
return self.__session.__enter__()
|
|
215
|
+
|
|
216
|
+
def __exit__(
|
|
217
|
+
self,
|
|
218
|
+
exc_type: type[BaseException] | None,
|
|
219
|
+
exc_val: BaseException | None,
|
|
220
|
+
exc_tb: types.TracebackType | None,
|
|
221
|
+
) -> None:
|
|
222
|
+
"""Exit the underlying session context."""
|
|
223
|
+
self.__session.__exit__(exc_type, exc_val, exc_tb)
|
|
224
|
+
|
|
225
|
+
def filter(
|
|
226
|
+
self,
|
|
227
|
+
param_ids: list[str] | None = None,
|
|
228
|
+
param_names: list[str] | None = None,
|
|
229
|
+
param_types: list[ParameterType] | None = None,
|
|
230
|
+
model_ids: list[str] | None = None,
|
|
231
|
+
) -> Self:
|
|
232
|
+
"""Further filter this context's parameters and aggregates.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
param_ids: Parameter IDs to keep, or None for no ID filtering.
|
|
236
|
+
param_names: Parameter names to keep, or None for no name filtering.
|
|
237
|
+
param_types: Parameter types to keep, or None for no type filtering.
|
|
238
|
+
model_ids: Model IDs to keep, or None for no model filtering.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
A new SessionContext scoped to the further-filtered views.
|
|
242
|
+
"""
|
|
243
|
+
with self:
|
|
244
|
+
filtered_param_view = filter_parameter_view(
|
|
245
|
+
self._param_filter_context,
|
|
246
|
+
parameter_ids=param_ids,
|
|
247
|
+
parameter_names=param_names,
|
|
248
|
+
parameter_types=param_types,
|
|
249
|
+
model_ids=model_ids,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
filtered_agg_view = filter_aggregate_view(
|
|
253
|
+
self._agg_filter_context,
|
|
254
|
+
parameter_names=param_names,
|
|
255
|
+
model_ids=model_ids,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return SessionContext(
|
|
259
|
+
self.__session, filtered_param_view, filtered_agg_view
|
|
260
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from diffract.core.data.nn.aggregates.view import AggregateView
|
|
2
|
+
from diffract.core.data.nn.params import ParameterType
|
|
3
|
+
from diffract.core.data.nn.params.interface import IParameterView
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def filter_parameter_view(
|
|
7
|
+
view: IParameterView,
|
|
8
|
+
*,
|
|
9
|
+
parameter_ids: list[str] | None = None,
|
|
10
|
+
parameter_names: list[str] | None = None,
|
|
11
|
+
parameter_types: list[ParameterType] | None = None,
|
|
12
|
+
model_ids: list[str] | None = None,
|
|
13
|
+
) -> IParameterView:
|
|
14
|
+
"""Apply the given filters to a parameter view.
|
|
15
|
+
|
|
16
|
+
Each filter is only applied when a non-empty value is provided. Scalar
|
|
17
|
+
values are wrapped into single-element lists before filtering.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
view: The parameter view to filter.
|
|
21
|
+
parameter_ids: Parameter IDs to select, or None to skip ID filtering.
|
|
22
|
+
parameter_names: Parameter names to filter by, or None to skip.
|
|
23
|
+
parameter_types: Parameter types to filter by, or None to skip.
|
|
24
|
+
model_ids: Model IDs to filter by, or None to skip.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The filtered parameter view.
|
|
28
|
+
"""
|
|
29
|
+
if parameter_names:
|
|
30
|
+
if isinstance(parameter_names, str):
|
|
31
|
+
parameter_names = [parameter_names]
|
|
32
|
+
view = view.filter_by_name(*parameter_names)
|
|
33
|
+
|
|
34
|
+
if parameter_types:
|
|
35
|
+
if isinstance(parameter_types, (str, ParameterType)):
|
|
36
|
+
parameter_types = [parameter_types]
|
|
37
|
+
view = view.filter_by_ptype(*parameter_types)
|
|
38
|
+
|
|
39
|
+
if model_ids:
|
|
40
|
+
if isinstance(model_ids, str):
|
|
41
|
+
model_ids = [model_ids]
|
|
42
|
+
view = view.filter_by_model_id(*model_ids)
|
|
43
|
+
|
|
44
|
+
if parameter_ids:
|
|
45
|
+
if isinstance(parameter_ids, (str, int)):
|
|
46
|
+
parameter_ids = [parameter_ids]
|
|
47
|
+
view = view[parameter_ids]
|
|
48
|
+
|
|
49
|
+
return view
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def filter_aggregate_view(
|
|
53
|
+
view: AggregateView,
|
|
54
|
+
*,
|
|
55
|
+
parameter_names: list[str] | None = None,
|
|
56
|
+
model_ids: list[str] | None = None,
|
|
57
|
+
) -> AggregateView:
|
|
58
|
+
"""Apply the given filters to an aggregate view.
|
|
59
|
+
|
|
60
|
+
Each filter is only applied when a non-empty value is provided. Scalar
|
|
61
|
+
values are wrapped into single-element lists before filtering.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
view: The aggregate view to filter.
|
|
65
|
+
parameter_names: Context parameter names to filter by, or None to skip.
|
|
66
|
+
model_ids: Context model IDs to filter by, or None to skip.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
The filtered aggregate view.
|
|
70
|
+
"""
|
|
71
|
+
if parameter_names:
|
|
72
|
+
if isinstance(parameter_names, str):
|
|
73
|
+
parameter_names = [parameter_names]
|
|
74
|
+
view = view.filter_by_context_params(*parameter_names)
|
|
75
|
+
|
|
76
|
+
if model_ids:
|
|
77
|
+
if isinstance(model_ids, str):
|
|
78
|
+
model_ids = [model_ids]
|
|
79
|
+
view = view.filter_by_context_models(*model_ids)
|
|
80
|
+
|
|
81
|
+
return view
|
diffract/viz/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Refactored visualization stack for diffract.
|
|
2
|
+
|
|
3
|
+
Submodules that need plotting backends are imported lazily, so the package
|
|
4
|
+
and its data helpers stay importable without the ``viz`` extra installed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"DARK_THEME",
|
|
11
|
+
"DEFAULT_THEME",
|
|
12
|
+
"MINIMAL_THEME",
|
|
13
|
+
"Plot",
|
|
14
|
+
"Theme",
|
|
15
|
+
"apply_theme",
|
|
16
|
+
"load_theme",
|
|
17
|
+
"plots",
|
|
18
|
+
"render",
|
|
19
|
+
"render_from_config",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
_RENDERER_EXPORTS = frozenset({"Plot", "load_theme", "render", "render_from_config"})
|
|
23
|
+
_STYLING_EXPORTS = frozenset(
|
|
24
|
+
{"DARK_THEME", "DEFAULT_THEME", "MINIMAL_THEME", "Theme", "apply_theme"}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def __getattr__(name: str) -> Any:
|
|
29
|
+
if name in _RENDERER_EXPORTS:
|
|
30
|
+
from . import renderer
|
|
31
|
+
|
|
32
|
+
return getattr(renderer, name)
|
|
33
|
+
if name in _STYLING_EXPORTS:
|
|
34
|
+
from . import styling
|
|
35
|
+
|
|
36
|
+
return getattr(styling, name)
|
|
37
|
+
if name == "plots":
|
|
38
|
+
from . import plots
|
|
39
|
+
|
|
40
|
+
return plots
|
|
41
|
+
msg = f"module {__name__!r} has no attribute {name!r}"
|
|
42
|
+
raise AttributeError(msg)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from .detection import detect_data_shape, detect_data_type, detect_field_meta
|
|
2
|
+
from .extraction import (
|
|
3
|
+
get_field_data,
|
|
4
|
+
get_field_value,
|
|
5
|
+
get_field_values,
|
|
6
|
+
)
|
|
7
|
+
from .filtering import apply_filter
|
|
8
|
+
from .ordering import Ordering, OrderMode, as_is, by_key, custom, lexicographic, numeric
|
|
9
|
+
from .provider import DataProvider
|
|
10
|
+
from .types import DataShape, DataType, Entry, EntryContext, FieldMeta, FieldRef
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"DataProvider",
|
|
14
|
+
"DataShape",
|
|
15
|
+
"DataType",
|
|
16
|
+
"Entry",
|
|
17
|
+
"EntryContext",
|
|
18
|
+
"FieldMeta",
|
|
19
|
+
"FieldRef",
|
|
20
|
+
"OrderMode",
|
|
21
|
+
"Ordering",
|
|
22
|
+
"apply_filter",
|
|
23
|
+
"as_is",
|
|
24
|
+
"by_key",
|
|
25
|
+
"custom",
|
|
26
|
+
"detect_data_shape",
|
|
27
|
+
"detect_data_type",
|
|
28
|
+
"detect_field_meta",
|
|
29
|
+
"get_field_data",
|
|
30
|
+
"get_field_value",
|
|
31
|
+
"get_field_values",
|
|
32
|
+
"lexicographic",
|
|
33
|
+
"numeric",
|
|
34
|
+
]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from .types import DataShape, DataType, FieldMeta
|
|
9
|
+
|
|
10
|
+
RE_NUMBER_PATTERN = re.compile(r"[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_number_regex(string: str) -> bool:
|
|
14
|
+
"""Return True if the string fully matches a numeric pattern."""
|
|
15
|
+
return RE_NUMBER_PATTERN.fullmatch(string) is not None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def detect_field_meta(values: list[Any]) -> FieldMeta:
|
|
19
|
+
"""Detect DataType and DataShape from values."""
|
|
20
|
+
data_type = detect_data_type(values)
|
|
21
|
+
data_shape = detect_data_shape(values)
|
|
22
|
+
return FieldMeta(data_type, data_shape)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def detect_data_type(values: list[Any]) -> DataType:
|
|
26
|
+
"""Detect if values are numeric or categorical."""
|
|
27
|
+
for v in values:
|
|
28
|
+
if v is None:
|
|
29
|
+
continue
|
|
30
|
+
if isinstance(v, bool):
|
|
31
|
+
return DataType.CATEGORICAL
|
|
32
|
+
if isinstance(v, str):
|
|
33
|
+
if is_number_regex(v):
|
|
34
|
+
continue
|
|
35
|
+
return DataType.CATEGORICAL
|
|
36
|
+
if isinstance(v, (int, float, np.integer, np.floating)):
|
|
37
|
+
continue
|
|
38
|
+
if isinstance(v, np.ndarray):
|
|
39
|
+
if not np.issubdtype(v.dtype, np.number):
|
|
40
|
+
return DataType.CATEGORICAL
|
|
41
|
+
continue
|
|
42
|
+
return DataType.CATEGORICAL
|
|
43
|
+
|
|
44
|
+
return DataType.NUMERIC
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def detect_data_shape(values: list[Any]) -> DataShape:
|
|
48
|
+
"""Detect if field represents scalar or vector per entry."""
|
|
49
|
+
for v in values:
|
|
50
|
+
if v is None:
|
|
51
|
+
continue
|
|
52
|
+
if isinstance(v, np.ndarray) and v.size > 1:
|
|
53
|
+
return DataShape.VECTOR
|
|
54
|
+
if isinstance(v, (list, tuple)) and len(v) > 1:
|
|
55
|
+
return DataShape.VECTOR
|
|
56
|
+
|
|
57
|
+
return DataShape.SCALAR
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from diffract.core.constants import CONTEXT_SEPARATOR, parse_contextual_field_name
|
|
7
|
+
|
|
8
|
+
from .detection import detect_field_meta
|
|
9
|
+
from .types import DataShape, DataType, Entry, EntryContext
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_field_value(ctx: EntryContext, field: str) -> Any:
|
|
13
|
+
"""Return the value of a field, resolving contextual field variants.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
ctx: Entry context to look the field up in.
|
|
17
|
+
field: Field name to resolve.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
The direct field value if present, otherwise the best-matching
|
|
21
|
+
contextual field value.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ValueError: If no matching field is found in the context.
|
|
25
|
+
"""
|
|
26
|
+
value = ctx.fields.get(field)
|
|
27
|
+
if value is not None:
|
|
28
|
+
return value
|
|
29
|
+
|
|
30
|
+
agg_pattern = re.compile(rf"^{re.escape(field)}{re.escape(CONTEXT_SEPARATOR)}.+$")
|
|
31
|
+
|
|
32
|
+
candidates: list[tuple[str, Any]] = []
|
|
33
|
+
for key_, value_ in ctx.fields.items():
|
|
34
|
+
if agg_pattern.match(key_):
|
|
35
|
+
candidates.append((key_, value_))
|
|
36
|
+
|
|
37
|
+
if not candidates:
|
|
38
|
+
raise ValueError(f"Field {field} not found in entry context")
|
|
39
|
+
|
|
40
|
+
return _select_best_contextual_field(candidates, ctx=ctx)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_field_values(entries: dict[str, Entry], field: str) -> list[Any]:
|
|
44
|
+
"""Return the field value for each entry.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
entries: Mapping of uid to entry.
|
|
48
|
+
field: Field name to extract.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List of field values, one per entry.
|
|
52
|
+
"""
|
|
53
|
+
values: list[Any] = []
|
|
54
|
+
|
|
55
|
+
for entry in entries.values():
|
|
56
|
+
value = get_field_value(EntryContext.from_entry(entry), field)
|
|
57
|
+
values.append(value)
|
|
58
|
+
|
|
59
|
+
return values
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_field_data(
|
|
63
|
+
entries: dict[str, Entry], field: str
|
|
64
|
+
) -> tuple[list[Any], DataType, DataShape]:
|
|
65
|
+
"""Return field values along with their detected type and shape.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
entries: Mapping of uid to entry.
|
|
69
|
+
field: Field name to extract.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Tuple of (values, data type, data shape).
|
|
73
|
+
"""
|
|
74
|
+
values = get_field_values(entries, field)
|
|
75
|
+
meta = detect_field_meta(values)
|
|
76
|
+
return values, meta.data_type, meta.data_shape
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _select_best_contextual_field(
|
|
80
|
+
candidates: list[tuple[str, Any]],
|
|
81
|
+
*,
|
|
82
|
+
ctx: EntryContext,
|
|
83
|
+
) -> Any:
|
|
84
|
+
"""Select best contextual field from multiple candidates.
|
|
85
|
+
|
|
86
|
+
Priority:
|
|
87
|
+
1. Fields whose context includes current parameter
|
|
88
|
+
2. Among those, prefer smaller context size (more specific)
|
|
89
|
+
3. Fallback: deterministic sort by field name
|
|
90
|
+
"""
|
|
91
|
+
scored: list[tuple[int, int, str, Any]] = []
|
|
92
|
+
|
|
93
|
+
for field_name, value in candidates:
|
|
94
|
+
_, models, params = parse_contextual_field_name(field_name)
|
|
95
|
+
|
|
96
|
+
model_match = models is None or (
|
|
97
|
+
ctx.model_id is not None and ctx.model_id in models
|
|
98
|
+
)
|
|
99
|
+
param_match = params is None or (
|
|
100
|
+
ctx.parameter_name is not None and ctx.parameter_name in params
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
context_size = (len(models) if models else 0) + (len(params) if params else 0)
|
|
104
|
+
|
|
105
|
+
scored.append(
|
|
106
|
+
(
|
|
107
|
+
1 if model_match else 0,
|
|
108
|
+
1 if param_match else 0,
|
|
109
|
+
-context_size,
|
|
110
|
+
field_name,
|
|
111
|
+
value,
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
scored.sort(key=lambda x: (-x[0], -x[1], -x[2], x[3]))
|
|
116
|
+
|
|
117
|
+
return scored[0][4]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from .extraction import get_field_value
|
|
8
|
+
from .types import Entry, EntryContext
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def apply_filter(
|
|
12
|
+
entries: dict[str, Entry],
|
|
13
|
+
value_filter: dict[str, tuple[str, Any]],
|
|
14
|
+
) -> dict[str, Entry]:
|
|
15
|
+
"""Apply value-based filtering to entries."""
|
|
16
|
+
return {
|
|
17
|
+
uid: entry
|
|
18
|
+
for uid, entry in entries.items()
|
|
19
|
+
if _entry_passes_filter(entry, value_filter)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _entry_passes_filter(
|
|
24
|
+
entry: Entry,
|
|
25
|
+
value_filter: dict[str, tuple[str, Any]],
|
|
26
|
+
) -> bool:
|
|
27
|
+
"""Check if an entry passes all filter conditions."""
|
|
28
|
+
ctx = EntryContext.from_entry(entry)
|
|
29
|
+
for field_name, (op, threshold) in value_filter.items():
|
|
30
|
+
value = get_field_value(ctx, field_name)
|
|
31
|
+
if not _check_condition(value, op, threshold):
|
|
32
|
+
return False
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _check_condition(value: Any, op: str, threshold: Any) -> bool:
|
|
37
|
+
"""Check a single filter condition."""
|
|
38
|
+
if value is None:
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
if isinstance(value, np.ndarray):
|
|
42
|
+
value = float(np.nanmean(value))
|
|
43
|
+
|
|
44
|
+
ops = {
|
|
45
|
+
">": lambda a, b: a > b,
|
|
46
|
+
"<": lambda a, b: a < b,
|
|
47
|
+
">=": lambda a, b: a >= b,
|
|
48
|
+
"<=": lambda a, b: a <= b,
|
|
49
|
+
"==": lambda a, b: a == b,
|
|
50
|
+
"!=": lambda a, b: a != b,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
op_func = ops.get(op)
|
|
54
|
+
if op_func is None:
|
|
55
|
+
raise ValueError(f"Unknown operator: {op}")
|
|
56
|
+
|
|
57
|
+
return op_func(value, threshold)
|