diffract-core 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. diffract/__init__.py +52 -0
  2. diffract/configs/fast_speed_without_disk.ini +48 -0
  3. diffract/configs/hybrid.ini +74 -0
  4. diffract/configs/sqlite.ini +62 -0
  5. diffract/containers.py +405 -0
  6. diffract/core/__init__.py +23 -0
  7. diffract/core/cache/__init__.py +24 -0
  8. diffract/core/cache/containers.py +93 -0
  9. diffract/core/cache/interface.py +117 -0
  10. diffract/core/cache/redis_manager.py +464 -0
  11. diffract/core/cache/simple_manager.py +478 -0
  12. diffract/core/compute/__init__.py +46 -0
  13. diffract/core/compute/config.py +48 -0
  14. diffract/core/compute/containers.py +81 -0
  15. diffract/core/compute/decorator.py +125 -0
  16. diffract/core/compute/exceptions.py +31 -0
  17. diffract/core/compute/execution/__init__.py +14 -0
  18. diffract/core/compute/execution/_types.py +70 -0
  19. diffract/core/compute/execution/aggregation.py +74 -0
  20. diffract/core/compute/execution/aggregation_runner.py +362 -0
  21. diffract/core/compute/execution/enums.py +38 -0
  22. diffract/core/compute/execution/executor.py +125 -0
  23. diffract/core/compute/execution/parameter_runner.py +125 -0
  24. diffract/core/compute/execution/restrictions.py +61 -0
  25. diffract/core/compute/execution/strategy.py +118 -0
  26. diffract/core/compute/execution/utils.py +112 -0
  27. diffract/core/compute/extensions/__init__.py +0 -0
  28. diffract/core/compute/extensions/power_law.py +1051 -0
  29. diffract/core/compute/extensions/rmt.py +150 -0
  30. diffract/core/compute/extensions/utils.py +36 -0
  31. diffract/core/compute/field_signature.py +183 -0
  32. diffract/core/compute/kernels/__init__.py +48 -0
  33. diffract/core/compute/kernels/alignment.py +106 -0
  34. diffract/core/compute/kernels/heavy_tailed.py +394 -0
  35. diffract/core/compute/kernels/marchenko_pastur.py +99 -0
  36. diffract/core/compute/kernels/mat_decomposition.py +101 -0
  37. diffract/core/compute/kernels/mat_properties.py +53 -0
  38. diffract/core/compute/kernels/model_quality.py +27 -0
  39. diffract/core/compute/kernels/norms.py +88 -0
  40. diffract/core/compute/kernels/ranks.py +35 -0
  41. diffract/core/compute/kernels/tracy_widom.py +36 -0
  42. diffract/core/compute/metadata.py +67 -0
  43. diffract/core/compute/registry.py +485 -0
  44. diffract/core/constants.py +147 -0
  45. diffract/core/data/__init__.py +32 -0
  46. diffract/core/data/interface.py +304 -0
  47. diffract/core/data/metadata/__init__.py +15 -0
  48. diffract/core/data/metadata/containers.py +60 -0
  49. diffract/core/data/metadata/interface.py +208 -0
  50. diffract/core/data/metadata/sqlite_index.py +604 -0
  51. diffract/core/data/nn/__init__.py +43 -0
  52. diffract/core/data/nn/aggregates/__init__.py +35 -0
  53. diffract/core/data/nn/aggregates/metadata.py +96 -0
  54. diffract/core/data/nn/aggregates/proxy.py +57 -0
  55. diffract/core/data/nn/aggregates/repository.py +87 -0
  56. diffract/core/data/nn/aggregates/view.py +307 -0
  57. diffract/core/data/nn/containers.py +86 -0
  58. diffract/core/data/nn/extractors/__init__.py +49 -0
  59. diffract/core/data/nn/extractors/base.py +300 -0
  60. diffract/core/data/nn/extractors/factory.py +234 -0
  61. diffract/core/data/nn/extractors/flax.py +112 -0
  62. diffract/core/data/nn/extractors/handlers/__init__.py +39 -0
  63. diffract/core/data/nn/extractors/handlers/base.py +110 -0
  64. diffract/core/data/nn/extractors/handlers/flax_handlers.py +71 -0
  65. diffract/core/data/nn/extractors/handlers/numpy_handlers.py +63 -0
  66. diffract/core/data/nn/extractors/handlers/onnx_handlers.py +67 -0
  67. diffract/core/data/nn/extractors/handlers/tensorflow_handlers.py +82 -0
  68. diffract/core/data/nn/extractors/handlers/torch_handlers.py +124 -0
  69. diffract/core/data/nn/extractors/interface.py +56 -0
  70. diffract/core/data/nn/extractors/numpy.py +94 -0
  71. diffract/core/data/nn/extractors/onnx.py +108 -0
  72. diffract/core/data/nn/extractors/tensorflow.py +99 -0
  73. diffract/core/data/nn/extractors/torch.py +204 -0
  74. diffract/core/data/nn/params/__init__.py +27 -0
  75. diffract/core/data/nn/params/interface.py +402 -0
  76. diffract/core/data/nn/params/metadata.py +110 -0
  77. diffract/core/data/nn/params/proxy.py +93 -0
  78. diffract/core/data/nn/params/repository.py +45 -0
  79. diffract/core/data/nn/params/schema.py +82 -0
  80. diffract/core/data/nn/params/view.py +393 -0
  81. diffract/core/data/proxy.py +246 -0
  82. diffract/core/data/repository.py +227 -0
  83. diffract/core/data/utils.py +33 -0
  84. diffract/core/data/view.py +389 -0
  85. diffract/core/export/__init__.py +27 -0
  86. diffract/core/export/containers.py +47 -0
  87. diffract/core/export/exporters.py +191 -0
  88. diffract/core/export/formatters/__init__.py +23 -0
  89. diffract/core/export/formatters/base.py +68 -0
  90. diffract/core/export/formatters/dict_formatter.py +38 -0
  91. diffract/core/export/formatters/json_formatter.py +58 -0
  92. diffract/core/export/formatters/list_formatter.py +41 -0
  93. diffract/core/export/formatters/pandas_formatter.py +86 -0
  94. diffract/core/export/formatters/polars_formatter.py +86 -0
  95. diffract/core/export/formatters/registry.py +84 -0
  96. diffract/core/export/interface.py +105 -0
  97. diffract/core/parallel/__init__.py +21 -0
  98. diffract/core/parallel/containers.py +57 -0
  99. diffract/core/parallel/execution.py +91 -0
  100. diffract/core/parallel/runtime.py +91 -0
  101. diffract/core/storage/__init__.py +35 -0
  102. diffract/core/storage/base_manager.py +330 -0
  103. diffract/core/storage/containers.py +122 -0
  104. diffract/core/storage/hdf5_manager.py +856 -0
  105. diffract/core/storage/hybrid_manager.py +218 -0
  106. diffract/core/storage/interface.py +222 -0
  107. diffract/core/storage/metadata.py +89 -0
  108. diffract/core/storage/ram_manager.py +159 -0
  109. diffract/core/storage/sqlite_manager.py +1062 -0
  110. diffract/core/storage/zarr_manager.py +992 -0
  111. diffract/core/utils/__init__.py +45 -0
  112. diffract/core/utils/build.py +46 -0
  113. diffract/core/utils/exceptions.py +11 -0
  114. diffract/core/utils/hashing.py +90 -0
  115. diffract/core/utils/imports.py +292 -0
  116. diffract/core/utils/math.py +15 -0
  117. diffract/py.typed +0 -0
  118. diffract/session/__init__.py +24 -0
  119. diffract/session/errors.py +17 -0
  120. diffract/session/field_cache.py +216 -0
  121. diffract/session/namespaces/__init__.py +15 -0
  122. diffract/session/namespaces/compute/__init__.py +219 -0
  123. diffract/session/namespaces/models/__init__.py +282 -0
  124. diffract/session/namespaces/models/parameters/__init__.py +133 -0
  125. diffract/session/namespaces/models/parameters/meta_patcher.py +167 -0
  126. diffract/session/namespaces/results/__init__.py +378 -0
  127. diffract/session/namespaces/results/eraser.py +129 -0
  128. diffract/session/namespaces/results/injester.py +249 -0
  129. diffract/session/namespaces/utils/__init__.py +141 -0
  130. diffract/session/namespaces/utils/merger.py +295 -0
  131. diffract/session/namespaces/viz/__init__.py +91 -0
  132. diffract/session/namespaces/viz/_utils.py +14 -0
  133. diffract/session/namespaces/viz/box.py +207 -0
  134. diffract/session/namespaces/viz/grid.py +160 -0
  135. diffract/session/namespaces/viz/heatmap.py +164 -0
  136. diffract/session/namespaces/viz/scatter.py +202 -0
  137. diffract/session/namespaces/viz/sparkline.py +270 -0
  138. diffract/session/namespaces/viz/violin.py +212 -0
  139. diffract/session/session.py +260 -0
  140. diffract/session/utils.py +81 -0
  141. diffract/viz/__init__.py +42 -0
  142. diffract/viz/data/__init__.py +34 -0
  143. diffract/viz/data/detection.py +57 -0
  144. diffract/viz/data/extraction.py +117 -0
  145. diffract/viz/data/filtering.py +57 -0
  146. diffract/viz/data/ordering.py +129 -0
  147. diffract/viz/data/provider.py +99 -0
  148. diffract/viz/data/types.py +98 -0
  149. diffract/viz/plots/__init__.py +37 -0
  150. diffract/viz/plots/base/__init__.py +20 -0
  151. diffract/viz/plots/base/axis.py +219 -0
  152. diffract/viz/plots/base/coloraxis.py +133 -0
  153. diffract/viz/plots/base/configurator.py +18 -0
  154. diffract/viz/plots/base/jitter.py +368 -0
  155. diffract/viz/plots/base/line.py +197 -0
  156. diffract/viz/plots/base/marker.py +278 -0
  157. diffract/viz/plots/base/overlay.py +14 -0
  158. diffract/viz/plots/base/plot.py +110 -0
  159. diffract/viz/plots/base/update.py +50 -0
  160. diffract/viz/plots/boxplot.py +283 -0
  161. diffract/viz/plots/cluster.py +374 -0
  162. diffract/viz/plots/heatmap.py +168 -0
  163. diffract/viz/plots/scatter.py +233 -0
  164. diffract/viz/plots/sparkline.py +405 -0
  165. diffract/viz/plots/subplots/__init__.py +32 -0
  166. diffract/viz/plots/subplots/coloraxis.py +339 -0
  167. diffract/viz/plots/subplots/factory.py +713 -0
  168. diffract/viz/plots/subplots/grid.py +214 -0
  169. diffract/viz/plots/subplots/layout.py +370 -0
  170. diffract/viz/plots/subplots/spec.py +39 -0
  171. diffract/viz/plots/violin.py +298 -0
  172. diffract/viz/renderer.py +513 -0
  173. diffract/viz/styling/__init__.py +78 -0
  174. diffract/viz/styling/palettes/__init__.py +20 -0
  175. diffract/viz/styling/palettes/bundle.py +16 -0
  176. diffract/viz/styling/palettes/color.py +83 -0
  177. diffract/viz/styling/palettes/dashes.py +27 -0
  178. diffract/viz/styling/palettes/symbols.py +42 -0
  179. diffract/viz/styling/resolvers/__init__.py +11 -0
  180. diffract/viz/styling/resolvers/categorical.py +68 -0
  181. diffract/viz/styling/resolvers/color.py +77 -0
  182. diffract/viz/styling/resolvers/numeric.py +108 -0
  183. diffract/viz/styling/sources.py +27 -0
  184. diffract/viz/styling/theme/__init__.py +29 -0
  185. diffract/viz/styling/theme/applier.py +114 -0
  186. diffract/viz/styling/theme/components.py +66 -0
  187. diffract/viz/styling/theme/presets.py +58 -0
  188. diffract/viz/styling/theme/theme.py +36 -0
  189. diffract_core-0.2.1.dist-info/METADATA +530 -0
  190. diffract_core-0.2.1.dist-info/RECORD +193 -0
  191. diffract_core-0.2.1.dist-info/WHEEL +4 -0
  192. diffract_core-0.2.1.dist-info/licenses/LICENSE +201 -0
  193. diffract_core-0.2.1.dist-info/licenses/NOTICE +2 -0
@@ -0,0 +1,68 @@
1
+ """Base utilities for DataFrame formatters.
2
+
3
+ Provides shared logic for building scalar and aggregate records.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from diffract.core.export.interface import AggregateData, ResultData
11
+
12
+ SCALAR_COLUMNS = ["model_id", "parameter_name", "parameter_uid", "parameter_type"]
13
+ AGGREGATE_COLUMNS = [
14
+ "field",
15
+ "context_models",
16
+ "context_params",
17
+ "value",
18
+ ]
19
+
20
+
21
+ def build_scalar_records(results: ResultData) -> list[dict[str, Any]]:
22
+ """Build scalar records from parameter results.
23
+
24
+ Args:
25
+ results: Parameter results dictionary.
26
+
27
+ Returns:
28
+ List of scalar records for DataFrame creation.
29
+ """
30
+ scalar_records: list[dict[str, Any]] = []
31
+
32
+ for param_uid, param_data in results.items():
33
+ metadata = param_data["metadata"]
34
+ field_values = param_data["fields"]
35
+
36
+ base_meta = {
37
+ "parameter_uid": param_uid,
38
+ "model_id": metadata["model_id"],
39
+ "parameter_name": metadata["name"],
40
+ "parameter_type": metadata["parameter_type"],
41
+ }
42
+
43
+ scalar_record: dict[str, Any] = {**base_meta}
44
+
45
+ for key, value in metadata.items():
46
+ if key not in ["model_id", "name", "parameter_type"]:
47
+ scalar_record[f"meta_{key}"] = value
48
+
49
+ scalar_record.update(field_values)
50
+
51
+ # Only add record if it has data beyond base metadata
52
+ if len(scalar_record) > len(base_meta):
53
+ scalar_records.append(scalar_record)
54
+
55
+ return scalar_records
56
+
57
+
58
+ def build_aggregate_records(aggregates: AggregateData) -> list[dict[str, Any]]:
59
+ """Build aggregate records from aggregate results.
60
+
61
+ Args:
62
+ aggregates: Aggregate results list.
63
+
64
+ Returns:
65
+ List of aggregate records for DataFrame creation.
66
+ """
67
+ # Aggregates are already in the right format, just return them
68
+ return list(aggregates)
@@ -0,0 +1,38 @@
1
+ """Dictionary formatter for result export.
2
+
3
+ Returns structured results with separate scalars and aggregates dictionaries.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from diffract.core.export.interface import (
9
+ AggregateData,
10
+ IResultFormatter,
11
+ ResultData,
12
+ StructuredExportResult,
13
+ )
14
+
15
+
16
+ class DictFormatter(IResultFormatter):
17
+ """Return results as structured dictionaries."""
18
+
19
+ def format_results(
20
+ self,
21
+ param_results: ResultData,
22
+ aggregate_results: AggregateData,
23
+ _fields: tuple[str, ...],
24
+ ) -> StructuredExportResult[dict]:
25
+ """Return results as structured dict with scalars and aggregates.
26
+
27
+ Args:
28
+ param_results: Parameter results dictionary with scalar fields.
29
+ aggregate_results: Aggregate results list with contextual fields.
30
+ _fields: Field names (unused for dict format).
31
+
32
+ Returns:
33
+ StructuredExportResult with scalars dict and aggregates list.
34
+ """
35
+ return StructuredExportResult(
36
+ scalars=param_results,
37
+ aggregates=aggregate_results,
38
+ )
@@ -0,0 +1,58 @@
1
+ """JSON formatter for result export.
2
+
3
+ Serializes structured results to a JSON string with a custom serializer that
4
+ converts array-like objects (e.g., numpy arrays, tensors) via tolist()
5
+ when available, and falls back to string representation otherwise.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+
12
+ from diffract.core.export.interface import (
13
+ AggregateData,
14
+ IResultFormatter,
15
+ ResultData,
16
+ )
17
+
18
+
19
+ class JsonFormatter(IResultFormatter):
20
+ """Convert results to a pretty-printed JSON string."""
21
+
22
+ def format_results(
23
+ self,
24
+ param_results: ResultData,
25
+ aggregate_results: AggregateData,
26
+ _fields: tuple[str, ...],
27
+ ) -> str:
28
+ """Convert results to formatted JSON string.
29
+
30
+ Args:
31
+ param_results: Parameter results dictionary with scalar fields.
32
+ aggregate_results: Aggregate results list with contextual fields.
33
+ _fields: Field names (unused for JSON format).
34
+
35
+ Returns:
36
+ JSON string representation with scalars and aggregates.
37
+ """
38
+ combined = {
39
+ "scalars": param_results,
40
+ "aggregates": aggregate_results,
41
+ }
42
+ return json.dumps(combined, indent=2, default=self._json_serializer)
43
+
44
+ def _json_serializer(self, obj: object) -> object:
45
+ """Custom JSON serializer for non-native types.
46
+
47
+ Uses tolist() for array-like structures to preserve value structure.
48
+ Falls back to str() for other objects.
49
+
50
+ Args:
51
+ obj: Object to serialize.
52
+
53
+ Returns:
54
+ JSON-serializable representation of the object.
55
+ """
56
+ if hasattr(obj, "tolist"):
57
+ return obj.tolist()
58
+ return str(obj)
@@ -0,0 +1,41 @@
1
+ """List formatter for result export.
2
+
3
+ Returns structured results with scalar records as list-of-dicts and
4
+ aggregates as list-of-dicts.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from diffract.core.export.interface import (
10
+ AggregateData,
11
+ IResultFormatter,
12
+ ResultData,
13
+ StructuredExportResult,
14
+ )
15
+
16
+ from .base import build_aggregate_records, build_scalar_records
17
+
18
+
19
+ class ListFormatter(IResultFormatter):
20
+ """Return results as list-based records."""
21
+
22
+ def format_results(
23
+ self,
24
+ param_results: ResultData,
25
+ aggregate_results: AggregateData,
26
+ _fields: tuple[str, ...],
27
+ ) -> StructuredExportResult[list[dict]]:
28
+ """Return results as lists of records.
29
+
30
+ Args:
31
+ param_results: Parameter results dictionary with scalar fields.
32
+ aggregate_results: Aggregate results list with contextual fields.
33
+ _fields: Field names (unused for list format).
34
+
35
+ Returns:
36
+ StructuredExportResult with scalars list and aggregates list.
37
+ """
38
+ return StructuredExportResult(
39
+ scalars=build_scalar_records(param_results),
40
+ aggregates=build_aggregate_records(aggregate_results),
41
+ )
@@ -0,0 +1,86 @@
1
+ """Pandas DataFrame formatter for result export.
2
+
3
+ Creates structured DataFrames separating scalar per-parameter fields from
4
+ contextual/aggregation fields. If pandas is unavailable, raises ImportError.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from typing import Any, Self
11
+
12
+ import diffract.core.utils.imports as import_utils
13
+ from diffract.core.export.interface import (
14
+ AggregateData,
15
+ IResultFormatter,
16
+ ResultData,
17
+ StructuredExportResult,
18
+ )
19
+
20
+ from .base import (
21
+ AGGREGATE_COLUMNS,
22
+ SCALAR_COLUMNS,
23
+ build_aggregate_records,
24
+ build_scalar_records,
25
+ )
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ if not import_utils.is_available("pandas"):
31
+ logger.debug("Failed to import pandas, disabling corresponding formatters")
32
+
33
+ class PandasFormatter(IResultFormatter):
34
+ """Stub formatter that raises when pandas is unavailable."""
35
+
36
+ def __new__(cls, *_args: Any, **_kwargs: Any) -> Self:
37
+ """Raise ImportError because pandas is not installed."""
38
+ msg = "pandas package not available"
39
+ raise ImportError(msg)
40
+
41
+ else:
42
+ pd = import_utils.require("pandas")
43
+
44
+ class PandasFormatter(IResultFormatter):
45
+ """Convert results to structured DataFrames separating scalars/aggregates."""
46
+
47
+ def format_results(
48
+ self,
49
+ param_results: ResultData,
50
+ aggregate_results: AggregateData,
51
+ fields: tuple[str, ...],
52
+ ) -> StructuredExportResult[pd.DataFrame]:
53
+ """Convert results to structured pandas DataFrames.
54
+
55
+ Args:
56
+ param_results: Parameter results dictionary with scalar fields.
57
+ aggregate_results: Aggregate results list with contextual fields.
58
+ fields: Field names requested for export.
59
+
60
+ Returns:
61
+ StructuredExportResult with scalars and aggregates DataFrames.
62
+ """
63
+ if not param_results and not aggregate_results:
64
+ return StructuredExportResult(
65
+ scalars=pd.DataFrame(columns=[*SCALAR_COLUMNS, *fields]),
66
+ aggregates=pd.DataFrame(columns=AGGREGATE_COLUMNS),
67
+ )
68
+
69
+ scalar_records = build_scalar_records(param_results)
70
+ aggregate_records = build_aggregate_records(aggregate_results)
71
+
72
+ scalars_df = (
73
+ pd.DataFrame(scalar_records)
74
+ if scalar_records
75
+ else pd.DataFrame(columns=[*SCALAR_COLUMNS, *fields])
76
+ )
77
+ aggregates_df = (
78
+ pd.DataFrame(aggregate_records)
79
+ if aggregate_records
80
+ else pd.DataFrame(columns=AGGREGATE_COLUMNS)
81
+ )
82
+
83
+ return StructuredExportResult(
84
+ scalars=scalars_df,
85
+ aggregates=aggregates_df,
86
+ )
@@ -0,0 +1,86 @@
1
+ """Polars DataFrame formatter for result export.
2
+
3
+ Creates structured DataFrames separating scalar per-parameter fields from
4
+ contextual/aggregation fields. If polars is unavailable, raises ImportError.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from typing import Any, Self
11
+
12
+ import diffract.core.utils.imports as import_utils
13
+ from diffract.core.export.interface import (
14
+ AggregateData,
15
+ IResultFormatter,
16
+ ResultData,
17
+ StructuredExportResult,
18
+ )
19
+
20
+ from .base import (
21
+ AGGREGATE_COLUMNS,
22
+ SCALAR_COLUMNS,
23
+ build_aggregate_records,
24
+ build_scalar_records,
25
+ )
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ if not import_utils.is_available("polars"):
31
+ logger.debug("Failed to import polars, disabling corresponding formatters")
32
+
33
+ class PolarsFormatter(IResultFormatter):
34
+ """Stub formatter that raises when polars is unavailable."""
35
+
36
+ def __new__(cls, *_args: Any, **_kwargs: Any) -> Self:
37
+ """Raise ImportError because polars is not installed."""
38
+ msg = "polars package not available"
39
+ raise ImportError(msg)
40
+
41
+ else:
42
+ pl = import_utils.require("polars")
43
+
44
+ class PolarsFormatter(IResultFormatter):
45
+ """Convert results to structured DataFrames separating scalars/aggregates."""
46
+
47
+ def format_results(
48
+ self,
49
+ param_results: ResultData,
50
+ aggregate_results: AggregateData,
51
+ fields: tuple[str, ...],
52
+ ) -> StructuredExportResult[pl.DataFrame]:
53
+ """Convert results to structured polars DataFrames.
54
+
55
+ Args:
56
+ param_results: Parameter results dictionary with scalar fields.
57
+ aggregate_results: Aggregate results list with contextual fields.
58
+ fields: Field names requested for export.
59
+
60
+ Returns:
61
+ StructuredExportResult with scalars and aggregates DataFrames.
62
+ """
63
+ if not param_results and not aggregate_results:
64
+ return StructuredExportResult(
65
+ scalars=pl.DataFrame({c: [] for c in [*SCALAR_COLUMNS, *fields]}),
66
+ aggregates=pl.DataFrame({c: [] for c in AGGREGATE_COLUMNS}),
67
+ )
68
+
69
+ scalar_records = build_scalar_records(param_results)
70
+ aggregate_records = build_aggregate_records(aggregate_results)
71
+
72
+ scalars_df = (
73
+ pl.DataFrame(scalar_records)
74
+ if scalar_records
75
+ else pl.DataFrame({c: [] for c in [*SCALAR_COLUMNS, *fields]})
76
+ )
77
+ aggregates_df = (
78
+ pl.DataFrame(aggregate_records)
79
+ if aggregate_records
80
+ else pl.DataFrame({c: [] for c in AGGREGATE_COLUMNS})
81
+ )
82
+
83
+ return StructuredExportResult(
84
+ scalars=scalars_df,
85
+ aggregates=aggregates_df,
86
+ )
@@ -0,0 +1,84 @@
1
+ """Formatter registry and factory for result export.
2
+
3
+ Maintains a mapping from format names to formatter instances and provides
4
+ helpers to register custom formatters and retrieve formatters by name.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import difflib
10
+ from typing import TYPE_CHECKING
11
+
12
+ import diffract.core.utils.imports as import_utils
13
+
14
+ from .dict_formatter import DictFormatter
15
+ from .json_formatter import JsonFormatter
16
+ from .list_formatter import ListFormatter
17
+
18
+ if TYPE_CHECKING:
19
+ from diffract.core.export.interface import IResultFormatter
20
+
21
+ # Formats backed by optional dependencies, mapped to the extra providing them
22
+ _OPTIONAL_FORMAT_EXTRAS: dict[str, str] = {
23
+ "pandas": "pandas",
24
+ "polars": "polars",
25
+ }
26
+
27
+ # Built-in formatter instances
28
+ FORMATTERS: dict[str, IResultFormatter] = {
29
+ "dict": DictFormatter(),
30
+ "json": JsonFormatter(),
31
+ "list": ListFormatter(),
32
+ }
33
+
34
+ if import_utils.is_available("pandas"):
35
+ from .pandas_formatter import PandasFormatter
36
+
37
+ FORMATTERS["pandas"] = PandasFormatter() # type: ignore[arg-type]
38
+
39
+ if import_utils.is_available("polars"):
40
+ from .polars_formatter import PolarsFormatter
41
+
42
+ FORMATTERS["polars"] = PolarsFormatter() # type: ignore[arg-type]
43
+
44
+
45
+ def register_formatter(name: str, formatter: IResultFormatter) -> None:
46
+ """Register a custom formatter under a given name.
47
+
48
+ Overwrites an existing formatter if the name is already used.
49
+
50
+ Args:
51
+ name: Unique format name.
52
+ formatter: Formatter instance to register.
53
+ """
54
+ FORMATTERS[name] = formatter
55
+
56
+
57
+ def get_formatter(name: str) -> IResultFormatter:
58
+ """Return a formatter by name or raise ValueError if unknown.
59
+
60
+ Args:
61
+ name: Format name to lookup.
62
+
63
+ Returns:
64
+ Formatter instance associated with the name.
65
+
66
+ Raises:
67
+ ValueError: If name is not registered.
68
+ """
69
+ try:
70
+ return FORMATTERS[name]
71
+ except KeyError as e:
72
+ if name in _OPTIONAL_FORMAT_EXTRAS:
73
+ extra = _OPTIONAL_FORMAT_EXTRAS[name]
74
+ msg = (
75
+ f"Export format '{name}' requires the optional dependency "
76
+ f"'{extra}'. Install it with: "
77
+ f'pip install "diffract-core[{extra}]".'
78
+ )
79
+ else:
80
+ known = ", ".join(sorted(FORMATTERS))
81
+ close = difflib.get_close_matches(name, FORMATTERS, n=3, cutoff=0.6)
82
+ hint = f" Did you mean: {', '.join(close)}?" if close else ""
83
+ msg = f"Unsupported format '{name}'. Known: {known}.{hint}"
84
+ raise ValueError(msg) from e
@@ -0,0 +1,105 @@
1
+ """Interfaces (protocols) for result export and formatting.
2
+
3
+ Defines:
4
+ - IResultFormatter: Converts structured results to a target format.
5
+ - IResultExporter: Extracts requested fields from parameters and aggregates,
6
+ then delegates formatting to an IResultFormatter.
7
+
8
+ ResultData structure (parameters):
9
+ {
10
+ "<param_uid>": {
11
+ "metadata": { ... },
12
+ "fields": { "field": value, ... }
13
+ },
14
+ ...
15
+ }
16
+
17
+ AggregateData structure:
18
+ [
19
+ {
20
+ "field": "field_name",
21
+ "context_models": ("model1", "model2"),
22
+ "context_params": ("param1",),
23
+ "value": ...,
24
+ },
25
+ ...
26
+ ]
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from dataclasses import dataclass
32
+ from typing import TYPE_CHECKING, Any, Protocol, TypeVar, runtime_checkable
33
+
34
+ if TYPE_CHECKING:
35
+ from diffract.core.data.nn.aggregates.view import AggregateView
36
+ from diffract.core.data.nn.params.interface import IParameterView
37
+
38
+ ResultData = dict[str, dict[str, Any]]
39
+ AggregateData = list[dict[str, Any]]
40
+
41
+ T = TypeVar("T")
42
+
43
+
44
+ @dataclass
45
+ class StructuredExportResult[T]:
46
+ """Container for structured export with separate scalar and aggregate data.
47
+
48
+ Scalars are per-parameter fields (e.g., frob_norm, stable_rank).
49
+ Aggregates are cross-entity fields from aggregation kernels (e.g., l_overlap).
50
+
51
+ Attributes:
52
+ scalars: DataFrame/dict with per-parameter metrics indexed by parameter.
53
+ aggregates: DataFrame/dict with aggregation results including context info.
54
+ """
55
+
56
+ scalars: T
57
+ aggregates: T
58
+
59
+
60
+ @runtime_checkable
61
+ class IResultFormatter(Protocol):
62
+ """Protocol for converting raw results to specific formats."""
63
+
64
+ def format_results(
65
+ self,
66
+ param_results: ResultData,
67
+ aggregate_results: AggregateData,
68
+ fields: tuple[str, ...],
69
+ ) -> Any:
70
+ """Convert raw results to the target format.
71
+
72
+ Args:
73
+ param_results: Parameter results dictionary with scalar fields.
74
+ aggregate_results: Aggregate results list with contextual fields.
75
+ fields: Field names requested for export.
76
+
77
+ Returns:
78
+ Formatted results in the target format (StructuredExportResult, dict, etc.).
79
+ """
80
+ ...
81
+
82
+
83
+ @runtime_checkable
84
+ class IResultExporter(Protocol):
85
+ """Protocol for exporting results from parameter and aggregate repositories."""
86
+
87
+ def export_results(
88
+ self,
89
+ *fields: tuple[str, ...],
90
+ parameters: IParameterView,
91
+ aggregates: AggregateView | None,
92
+ formatter: IResultFormatter,
93
+ ) -> Any:
94
+ """Export results from parameters and aggregates using the specified formatter.
95
+
96
+ Args:
97
+ *fields: Field names to export.
98
+ parameters: Parameter collection to export scalar fields from.
99
+ aggregates: Aggregate view to export contextual fields from.
100
+ formatter: Formatter to use for output conversion.
101
+
102
+ Returns:
103
+ Formatted results from the formatter.
104
+ """
105
+ ...
@@ -0,0 +1,21 @@
1
+ """Parallel execution utilities for compute subsystem."""
2
+
3
+ from .containers import ParallelSingletonContainer
4
+ from .execution import map_maybe_parallel
5
+ from .runtime import (
6
+ ParallelCalibration,
7
+ ParallelContext,
8
+ calibrate_thread_pool_overhead,
9
+ get_thread_pool_calibration,
10
+ should_parallelize,
11
+ )
12
+
13
+ __all__ = [
14
+ "ParallelCalibration",
15
+ "ParallelContext",
16
+ "ParallelSingletonContainer",
17
+ "calibrate_thread_pool_overhead",
18
+ "get_thread_pool_calibration",
19
+ "map_maybe_parallel",
20
+ "should_parallelize",
21
+ ]
@@ -0,0 +1,57 @@
1
+ """Dependency injection container for pool-per-method executors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import concurrent.futures
6
+ import os
7
+ from typing import Any
8
+
9
+ from dependency_injector import containers, providers
10
+
11
+ from .runtime import ParallelContext, get_thread_pool_calibration
12
+
13
+
14
+ def _normalize_workers(max_workers: int | None) -> int:
15
+ if max_workers is None:
16
+ return max(1, os.cpu_count() or 1)
17
+ return max(1, int(max_workers))
18
+
19
+
20
+ def _thread_pool_context_resource(*, workers: int) -> Any:
21
+ executor = concurrent.futures.ThreadPoolExecutor(max_workers=workers)
22
+ try:
23
+ calibration = get_thread_pool_calibration(workers)
24
+ yield ParallelContext(
25
+ executor=executor, calibration=calibration, workers=workers
26
+ )
27
+ finally:
28
+ executor.shutdown(wait=True, cancel_futures=True)
29
+
30
+
31
+ def _process_pool_context_resource(*, workers: int) -> Any:
32
+ executor = concurrent.futures.ProcessPoolExecutor(max_workers=workers)
33
+ try:
34
+ yield executor
35
+ finally:
36
+ executor.shutdown(wait=True, cancel_futures=True)
37
+
38
+
39
+ class ParallelSingletonContainer(containers.DeclarativeContainer):
40
+ """Singleton container for project-wide parallelism resources."""
41
+
42
+ config = providers.Configuration()
43
+
44
+ max_workers = providers.Callable(_normalize_workers, config.thread_pool.max_workers)
45
+ process_max_workers = providers.Callable(
46
+ _normalize_workers, config.process_pool.max_workers
47
+ )
48
+
49
+ thread_pool_context = providers.Resource(
50
+ _thread_pool_context_resource,
51
+ workers=max_workers,
52
+ )
53
+
54
+ process_pool_context = providers.Resource(
55
+ _process_pool_context_resource,
56
+ workers=process_max_workers,
57
+ )