equiframe-benchmark 0.12.4__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 (62) hide show
  1. equiframe_benchmark/PROTOCOL_V2.md +172 -0
  2. equiframe_benchmark/__init__.py +284 -0
  3. equiframe_benchmark/__main__.py +3 -0
  4. equiframe_benchmark/_scale_workload.py +375 -0
  5. equiframe_benchmark/calibration_types.py +112 -0
  6. equiframe_benchmark/cli.py +196 -0
  7. equiframe_benchmark/commands.py +149 -0
  8. equiframe_benchmark/commands_scale.py +38 -0
  9. equiframe_benchmark/commands_step10.py +275 -0
  10. equiframe_benchmark/commands_v2.py +206 -0
  11. equiframe_benchmark/cost_table.py +62 -0
  12. equiframe_benchmark/costs.py +788 -0
  13. equiframe_benchmark/distribution.py +932 -0
  14. equiframe_benchmark/evaluation.py +614 -0
  15. equiframe_benchmark/evaluation_v2.py +428 -0
  16. equiframe_benchmark/fixtures.py +299 -0
  17. equiframe_benchmark/freeze.py +869 -0
  18. equiframe_benchmark/freeze_v2.py +412 -0
  19. equiframe_benchmark/integration.py +655 -0
  20. equiframe_benchmark/io.py +29 -0
  21. equiframe_benchmark/models.py +313 -0
  22. equiframe_benchmark/models_v2.py +13 -0
  23. equiframe_benchmark/mutations.py +34 -0
  24. equiframe_benchmark/oracle.py +264 -0
  25. equiframe_benchmark/pairwise.py +248 -0
  26. equiframe_benchmark/pilot.py +254 -0
  27. equiframe_benchmark/py.typed +0 -0
  28. equiframe_benchmark/reconciliation.py +294 -0
  29. equiframe_benchmark/reproduction.py +542 -0
  30. equiframe_benchmark/scale_io.py +19 -0
  31. equiframe_benchmark/scale_process.py +1077 -0
  32. equiframe_benchmark/scale_types.py +120 -0
  33. equiframe_benchmark/scaling.py +529 -0
  34. equiframe_benchmark/schemas/protocol-v2/CommonSupportV2-2.0.schema.json +327 -0
  35. equiframe_benchmark/schemas/protocol-v2/ContrastSpecV2-2.0.schema.json +37 -0
  36. equiframe_benchmark/schemas/protocol-v2/EvaluationConfigV2-2.0.schema.json +184 -0
  37. equiframe_benchmark/schemas/protocol-v2/EvaluationReportV2-2.0.schema.json +1237 -0
  38. equiframe_benchmark/schemas/protocol-v2/EvaluationStratumV2-2.0.schema.json +747 -0
  39. equiframe_benchmark/schemas/protocol-v2/FreezeDefinitionV2-2.0.schema.json +5951 -0
  40. equiframe_benchmark/schemas/protocol-v2/FreezeManifestV2-2.0.schema.json +6222 -0
  41. equiframe_benchmark/schemas/protocol-v2/PrincipalContrastV2-2.0.schema.json +578 -0
  42. equiframe_benchmark/schemas/protocol-v2/ProtocolReviewV2-2.0.schema.json +56 -0
  43. equiframe_benchmark/schemas/protocol-v2/SignedMetricV2-2.0.schema.json +51 -0
  44. equiframe_benchmark/schemas/protocol-v2/StudyCaseResultV2-2.0.schema.json +4881 -0
  45. equiframe_benchmark/schemas/protocol-v2/StudyResultV2-2.0.schema.json +8370 -0
  46. equiframe_benchmark/schemas/protocol-v2/TableExportV2-2.0.schema.json +75 -0
  47. equiframe_benchmark/selection.py +178 -0
  48. equiframe_benchmark/snapshot.py +24 -0
  49. equiframe_benchmark/splits.py +92 -0
  50. equiframe_benchmark/study.py +526 -0
  51. equiframe_benchmark/study_bundle.py +379 -0
  52. equiframe_benchmark/study_bundle_v2.py +388 -0
  53. equiframe_benchmark/study_v2.py +544 -0
  54. equiframe_benchmark/tables.py +347 -0
  55. equiframe_benchmark/tables_v2.py +340 -0
  56. equiframe_benchmark/training_types.py +52 -0
  57. equiframe_benchmark-0.12.4.dist-info/METADATA +343 -0
  58. equiframe_benchmark-0.12.4.dist-info/RECORD +62 -0
  59. equiframe_benchmark-0.12.4.dist-info/WHEEL +4 -0
  60. equiframe_benchmark-0.12.4.dist-info/entry_points.txt +3 -0
  61. equiframe_benchmark-0.12.4.dist-info/licenses/LICENSE +21 -0
  62. equiframe_benchmark-0.12.4.dist-info/licenses/THIRD_PARTY_NOTICES.md +8 -0
@@ -0,0 +1,299 @@
1
+ """Five manually specified synthetic fixtures; adapted from the step 2 native examples."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import dataclass
7
+ from datetime import date, datetime
8
+ from decimal import Decimal
9
+ from typing import TYPE_CHECKING
10
+
11
+ import equiframe as ef
12
+ import pyarrow as pa
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Mapping
16
+
17
+ import pandas as pd
18
+ import polars as pl
19
+
20
+
21
+ def column(name: str, dtype: ef.LogicalType) -> ef.ColumnSpec:
22
+ numeric = isinstance(dtype, (ef.IntegerType, ef.FloatType, ef.DecimalType))
23
+ temporal: ef.DatePolicy | ef.TimestampPolicy | None = None
24
+ if isinstance(dtype, ef.DateType):
25
+ temporal = ef.DatePolicy(closed="both")
26
+ elif isinstance(dtype, ef.TimestampType):
27
+ temporal = ef.TimestampPolicy(
28
+ granularity=dtype.unit,
29
+ timezone=dtype.timezone,
30
+ naive="reject" if dtype.timezone else "preserve",
31
+ comparison="instant" if dtype.timezone else "wall_time",
32
+ ambiguous="reject",
33
+ closed="both",
34
+ )
35
+ return ef.ColumnSpec(
36
+ name=name,
37
+ logical_type=dtype,
38
+ nullable=True,
39
+ null_policy=ef.NullPolicy(representations=("null",)),
40
+ nan_policy=ef.NaNPolicy(handling="distinct") if isinstance(dtype, ef.FloatType) else None,
41
+ numeric_policy=ef.NumericPolicy(comparison="exact", unit="synthetic_units")
42
+ if numeric
43
+ else None,
44
+ temporal_policy=temporal,
45
+ allowed_values=(),
46
+ )
47
+
48
+
49
+ def schema(columns: tuple[ef.ColumnSpec, ...]) -> ef.TableSchema:
50
+ return ef.TableSchema(
51
+ columns=columns, keys=(), multiplicity="preserve", order=ef.IgnoredOrder()
52
+ )
53
+
54
+
55
+ def contract(
56
+ family: ef.Family,
57
+ inputs: dict[str, tuple[ef.ColumnSpec, ...]],
58
+ output: tuple[ef.ColumnSpec, ...],
59
+ ) -> ef.Contract:
60
+ return ef.Contract(
61
+ schema_version="1.0",
62
+ contract_id="synthetic." + family,
63
+ revision=1,
64
+ family=family,
65
+ inputs=tuple(ef.TableSpec(name=n, schema_spec=schema(c)) for n, c in inputs.items()),
66
+ output=ef.TableSpec(name="result", schema_spec=schema(output)),
67
+ preconditions=(),
68
+ rules=(
69
+ ef.RuleSpec(
70
+ rule_id="output_schema",
71
+ revision=1,
72
+ required=True,
73
+ severity="error",
74
+ applicability=(),
75
+ specification=ef.SchemaCheck(table="result"),
76
+ ),
77
+ ),
78
+ )
79
+
80
+
81
+ @dataclass
82
+ class Case:
83
+ contract: ef.Contract
84
+ inputs: dict[str, pa.Table]
85
+ expected: list[dict[str, object]]
86
+
87
+
88
+ def cases() -> list[Case]:
89
+ text = ef.StringType()
90
+ integer = ef.IntegerType(bits=64, signed=True)
91
+ decimal = ef.DecimalType(precision=18, scale=2)
92
+ retail = (
93
+ column("id", text),
94
+ column("amount", decimal),
95
+ column("measurement", ef.FloatType(bits=64)),
96
+ )
97
+ sales = pa.table(
98
+ {
99
+ "id": pa.array(["001", "001", "002", "003"], type=pa.string()),
100
+ "amount": pa.array(
101
+ [Decimal("10.20"), Decimal("10.20"), None, Decimal("0.00")],
102
+ type=pa.decimal128(18, 2),
103
+ ),
104
+ "measurement": pa.array(
105
+ [None, float("nan"), 0.0, 2.5], type=pa.float64(), from_pandas=False
106
+ ),
107
+ }
108
+ )
109
+ observed: list[dict[str, object]] = [
110
+ {"id": "001", "amount": Decimal("10.20"), "measurement": None},
111
+ {"id": "001", "amount": Decimal("10.20"), "measurement": float("nan")},
112
+ {"id": "002", "amount": None, "measurement": 0.0},
113
+ {"id": "003", "amount": Decimal("0.00"), "measurement": 2.5},
114
+ ]
115
+ orders = (column("id", text), column("product_id", text))
116
+ products = (column("product_id", text), column("category", text))
117
+ joined = (*orders, column("category", text))
118
+ grouped = (column("warehouse", text), column("amount", decimal), column("id", text))
119
+ totals = (
120
+ column("warehouse", text),
121
+ column("total", ef.DecimalType(precision=38, scale=2)),
122
+ column("rows", integer),
123
+ )
124
+ stamps = (
125
+ column("sensor_id", text),
126
+ column("recorded_at", ef.TimestampType(unit="us", timezone="UTC")),
127
+ )
128
+ days = (column("sensor_id", text), column("day", ef.DateType()))
129
+ return [
130
+ Case(contract("nulls_types", {"items": retail}, retail), {"items": sales}, observed),
131
+ Case(contract("duplicates_order", {"items": retail}, retail), {"items": sales}, observed),
132
+ Case(
133
+ contract("joins", {"orders": orders, "products": products}, joined),
134
+ {
135
+ "orders": pa.table({"id": ["001", "002"], "product_id": ["A", "Z"]}),
136
+ "products": pa.table({"product_id": ["A"], "category": ["books"]}),
137
+ },
138
+ [
139
+ {"id": "001", "product_id": "A", "category": "books"},
140
+ {"id": "002", "product_id": "Z", "category": None},
141
+ ],
142
+ ),
143
+ Case(
144
+ contract("aggregations", {"items": grouped}, totals),
145
+ {
146
+ "items": pa.table(
147
+ {
148
+ "warehouse": ["east", "east", "east", "west"],
149
+ "amount": pa.array(
150
+ [Decimal("10.20"), None, Decimal("0.00"), Decimal("20.40")],
151
+ type=pa.decimal128(18, 2),
152
+ ),
153
+ "id": ["001", "002", "003", "004"],
154
+ }
155
+ )
156
+ },
157
+ [
158
+ {"warehouse": "east", "total": Decimal("10.20"), "rows": 3},
159
+ {"warehouse": "west", "total": Decimal("20.40"), "rows": 1},
160
+ ],
161
+ ),
162
+ Case(
163
+ contract("dates", {"readings": stamps}, days),
164
+ {
165
+ "readings": pa.table(
166
+ {
167
+ "sensor_id": ["0007", "0008"],
168
+ "recorded_at": pa.array(
169
+ [
170
+ datetime.fromisoformat("2024-02-29T23:59:59.123456+00:00"),
171
+ None,
172
+ ],
173
+ type=pa.timestamp("us", tz="UTC"),
174
+ ),
175
+ }
176
+ )
177
+ },
178
+ [{"sensor_id": "0007", "day": date(2024, 2, 29)}, {"sensor_id": "0008", "day": None}],
179
+ ),
180
+ ]
181
+
182
+
183
+ def pandas_transform(tables: Mapping[str, pd.DataFrame], ctx: ef.RunContext) -> pd.DataFrame:
184
+ family = ctx.plan.contract.family
185
+ if family == "joins":
186
+ return tables["orders"].merge(tables["products"], on="product_id", how="left", sort=False)
187
+ if family == "aggregations":
188
+ return (
189
+ tables["items"]
190
+ .groupby("warehouse", dropna=False, sort=False)
191
+ .agg(
192
+ total=("amount", "sum"),
193
+ rows=("id", "size"),
194
+ )
195
+ .reset_index()
196
+ )
197
+ if family == "dates":
198
+ import pandas as pd
199
+
200
+ frame = tables["readings"]
201
+ # This fixture declares UTC microseconds. Use Pandas' own temporal
202
+ # implementation: Arrow-backed .dt.date needs an additional C++ IANA
203
+ # database on some Windows wheels, independently of Python tzdata.
204
+ recorded_at: pd.Series[pd.Timestamp] = pd.Series(
205
+ frame["recorded_at"].tolist(), dtype="datetime64[us, UTC]", index=frame.index
206
+ )
207
+ day: pd.Series[date] = recorded_at.dt.date.astype(pd.ArrowDtype(pa.date32()))
208
+ return frame[["sensor_id"]].assign(day=day)
209
+ return tables["items"].copy(deep=True)
210
+
211
+
212
+ def polars_transform(tables: Mapping[str, pl.DataFrame], ctx: ef.RunContext) -> pl.LazyFrame:
213
+ import polars as pl
214
+
215
+ family = ctx.plan.contract.family
216
+ if family == "joins":
217
+ return (
218
+ tables["orders"]
219
+ .lazy()
220
+ .join(tables["products"].lazy(), on="product_id", how="left", maintain_order="left")
221
+ )
222
+ if family == "aggregations":
223
+ return (
224
+ tables["items"]
225
+ .lazy()
226
+ .group_by("warehouse", maintain_order=True)
227
+ .agg(
228
+ pl.col("amount").sum().alias("total"),
229
+ pl.len().alias("rows"),
230
+ )
231
+ )
232
+ if family == "dates":
233
+ return (
234
+ tables["readings"]
235
+ .lazy()
236
+ .select("sensor_id", pl.col("recorded_at").dt.date().alias("day"))
237
+ )
238
+ return tables["items"].lazy().select(pl.all())
239
+
240
+
241
+ def registry() -> ef.TransformationRegistry:
242
+ result = ef.TransformationRegistry()
243
+ sql = {
244
+ "nulls_types": 'SELECT * FROM "items"',
245
+ "duplicates_order": 'SELECT * FROM "items"',
246
+ "joins": 'SELECT o.id, o.product_id, p.category FROM "orders" o '
247
+ 'LEFT JOIN "products" p ON o.product_id=p.product_id ORDER BY o.id',
248
+ "aggregations": 'SELECT warehouse, sum(amount) AS total, count(*) AS rows FROM "items" '
249
+ "GROUP BY warehouse",
250
+ "dates": 'SELECT sensor_id, CAST(recorded_at AS DATE) AS day FROM "readings"',
251
+ }
252
+ for family, query in sql.items():
253
+ result.register(ef.PandasTransformation(family, pandas_transform, "synthetic.v1"))
254
+ result.register(ef.PolarsTransformation(family, polars_transform, "synthetic.v1"))
255
+ result.register(ef.DuckDBTransformation(family, query, "synthetic.v1"))
256
+ return result
257
+
258
+
259
+ def assert_expected(actual: list[dict[str, object]], expected: list[dict[str, object]]) -> None:
260
+ if not (len(actual) == len(expected)):
261
+ raise AssertionError("Internal invariant check failed.")
262
+ for observed, wanted in zip(actual, expected, strict=True):
263
+ if not (observed.keys() == wanted.keys()):
264
+ raise AssertionError("Internal invariant check failed.")
265
+ for key, value in wanted.items():
266
+ if isinstance(value, float) and math.isnan(value):
267
+ actual_value = observed[key]
268
+ if not (isinstance(actual_value, float) and math.isnan(actual_value)):
269
+ raise AssertionError("Internal invariant check failed.")
270
+ else:
271
+ if not (observed[key] == value):
272
+ raise AssertionError("Internal invariant check failed.")
273
+
274
+
275
+ def expected_table(case: Case) -> pa.Table:
276
+ """Manually specified expected physical schemas, independent of normalization."""
277
+ schemas = {
278
+ "nulls_types": pa.schema(
279
+ [("id", pa.string()), ("amount", pa.decimal128(18, 2)), ("measurement", pa.float64())]
280
+ ),
281
+ "duplicates_order": pa.schema(
282
+ [("id", pa.string()), ("amount", pa.decimal128(18, 2)), ("measurement", pa.float64())]
283
+ ),
284
+ "joins": pa.schema(
285
+ [("id", pa.string()), ("product_id", pa.string()), ("category", pa.string())]
286
+ ),
287
+ "aggregations": pa.schema(
288
+ [("warehouse", pa.string()), ("total", pa.decimal128(38, 2)), ("rows", pa.int64())]
289
+ ),
290
+ "dates": pa.schema([("sensor_id", pa.string()), ("day", pa.date32())]),
291
+ }
292
+ return pa.Table.from_pylist(case.expected, schema=schemas[case.contract.family])
293
+
294
+
295
+ COMPARISON_POLICY = ef.ComparisonPolicy(
296
+ physical_types="lossless",
297
+ index="ignore",
298
+ justification="Only declared columns carry meaning; lossless native widths allowed.",
299
+ )