calc-flow-python 4.0.0__cp313-abi3-win_amd64.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.
@@ -0,0 +1,662 @@
1
+ """Immutable symbolic expressions for tables, columns, arrays, parameters.
2
+
3
+ Expression objects are declaration-only values: they compose into a node
4
+ graph, expose a canonical digest and a deterministic explanation, and offer
5
+ no data-execution path. Operator dunders build new expressions; ``==`` and
6
+ friends never produce Python booleans, and structural identity is available
7
+ only through ``identical()``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from collections.abc import Sequence
13
+ from dataclasses import dataclass
14
+ from typing import TYPE_CHECKING, Literal, overload
15
+
16
+ from calc_flow.symbolic.domains import (
17
+ array_operator_error,
18
+ bool_error,
19
+ column_operator_error,
20
+ is_strict_scalar_type,
21
+ namespace_error,
22
+ table_operator_error,
23
+ type_name,
24
+ )
25
+ from calc_flow.symbolic.nodes import (
26
+ CBool,
27
+ CDType,
28
+ CEnum,
29
+ CInt,
30
+ CMap,
31
+ CNull,
32
+ CSeq,
33
+ CStr,
34
+ Node,
35
+ build,
36
+ explain_node,
37
+ literal_value,
38
+ )
39
+ from calc_flow.symbolic.types import (
40
+ BatchKind,
41
+ Field,
42
+ ScalarLiteral,
43
+ check_array_dtype,
44
+ check_table_field_type,
45
+ require_non_empty_str,
46
+ require_non_negative_int,
47
+ )
48
+
49
+ if TYPE_CHECKING:
50
+ from calc_flow.symbolic.program import FeatureSet
51
+
52
+ type ColumnOperand = ColumnExpr | ScalarLiteral
53
+ type ArrayOperand = ArrayExpr | Parameter[ArrayExpr] | ScalarLiteral
54
+
55
+
56
+ @dataclass(frozen=True, slots=True, eq=False, repr=False)
57
+ class Expr[T]:
58
+ """Base class of every immutable symbolic expression."""
59
+
60
+ _node: Node
61
+
62
+ @property
63
+ def digest(self) -> str:
64
+ """The lowercase hexadecimal v1 node digest of this declaration."""
65
+
66
+ return self._node.digest
67
+
68
+ def identical(self, other: object, /) -> bool:
69
+ """Compare the complete normalized structure of two expressions."""
70
+
71
+ if not isinstance(other, Expr):
72
+ return False
73
+ return (
74
+ self._node.digest == other._node.digest
75
+ and self._node.node_bytes == other._node.node_bytes
76
+ )
77
+
78
+ def explain(self) -> str:
79
+ """Render the declaration tree deterministically."""
80
+
81
+ return explain_node(self._node)
82
+
83
+ def __bool__(self) -> bool:
84
+ raise bool_error()
85
+
86
+ def __repr__(self) -> str:
87
+ return f"{type(self).__qualname__}(digest={self._node.digest})"
88
+
89
+ __hash__ = None
90
+
91
+
92
+ def _literal_node(value: ScalarLiteral, /) -> Node:
93
+ return build("literal", (), {"value": literal_value(value)})
94
+
95
+
96
+ def _column_operand(operand: object, operator: str, /) -> Node:
97
+ if isinstance(operand, ColumnExpr):
98
+ return operand._node
99
+ if is_strict_scalar_type(operand):
100
+ return _literal_node(operand) # type: ignore[arg-type]
101
+ raise column_operator_error(operator, operand)
102
+
103
+
104
+ def _array_operand(operand: object, operator: str, /) -> Node:
105
+ if isinstance(operand, ArrayExpr):
106
+ return operand._node
107
+ if isinstance(operand, Parameter) and operand.kind == "array":
108
+ return operand._node
109
+ if is_strict_scalar_type(operand):
110
+ return _literal_node(operand) # type: ignore[arg-type]
111
+ raise array_operator_error(operator, operand)
112
+
113
+
114
+ def _column_binary(
115
+ left: ColumnExpr, right: object, primitive: str, operator: str, /
116
+ ) -> ColumnExpr:
117
+ return ColumnExpr(
118
+ build(
119
+ primitive,
120
+ (
121
+ _column_operand(left, operator),
122
+ _column_operand(right, operator),
123
+ ),
124
+ {},
125
+ )
126
+ )
127
+
128
+
129
+ def _array_binary(
130
+ left: ArrayExpr, right: object, primitive: str, operator: str, /
131
+ ) -> ArrayExpr:
132
+ return ArrayExpr(
133
+ build(
134
+ primitive,
135
+ (_array_operand(left, operator), _array_operand(right, operator)),
136
+ {},
137
+ )
138
+ )
139
+
140
+
141
+ @dataclass(frozen=True, slots=True, eq=False, repr=False)
142
+ class ColumnExpr(Expr[object]):
143
+ """A row-local column expression over one table lineage."""
144
+
145
+ def __eq__(self, other: ColumnOperand, /) -> ColumnExpr:
146
+ return _column_binary(self, other, "eq", "==")
147
+
148
+ def __ne__(self, other: ColumnOperand, /) -> ColumnExpr:
149
+ return _column_binary(self, other, "ne", "!=")
150
+
151
+ def __lt__(self, other: ColumnOperand, /) -> ColumnExpr:
152
+ return _column_binary(self, other, "lt", "<")
153
+
154
+ def __le__(self, other: ColumnOperand, /) -> ColumnExpr:
155
+ return _column_binary(self, other, "le", "<=")
156
+
157
+ def __gt__(self, other: ColumnOperand, /) -> ColumnExpr:
158
+ return _column_binary(self, other, "gt", ">")
159
+
160
+ def __ge__(self, other: ColumnOperand, /) -> ColumnExpr:
161
+ return _column_binary(self, other, "ge", ">=")
162
+
163
+ def __add__(self, other: ColumnOperand, /) -> ColumnExpr:
164
+ return _column_binary(self, other, "add", "+")
165
+
166
+ def __radd__(self, other: ColumnOperand, /) -> ColumnExpr:
167
+ return _column_binary(self, other, "add", "+")
168
+
169
+ def __sub__(self, other: ColumnOperand, /) -> ColumnExpr:
170
+ return _column_binary(self, other, "sub", "-")
171
+
172
+ def __rsub__(self, other: ColumnOperand, /) -> ColumnExpr:
173
+ return _reflected_column(self, other, "sub", "-")
174
+
175
+ def __mul__(self, other: ColumnOperand, /) -> ColumnExpr:
176
+ return _column_binary(self, other, "mul", "*")
177
+
178
+ def __rmul__(self, other: ColumnOperand, /) -> ColumnExpr:
179
+ return _column_binary(self, other, "mul", "*")
180
+
181
+ def __truediv__(self, other: ColumnOperand, /) -> ColumnExpr:
182
+ return _column_binary(self, other, "truediv", "/")
183
+
184
+ def __rtruediv__(self, other: ColumnOperand, /) -> ColumnExpr:
185
+ return _reflected_column(self, other, "truediv", "/")
186
+
187
+ def __neg__(self, /) -> ColumnExpr:
188
+ return ColumnExpr(build("neg", (self._node,), {}))
189
+
190
+ def __and__(self, other: ColumnOperand, /) -> ColumnExpr:
191
+ return _column_binary(self, other, "and", "&")
192
+
193
+ def __rand__(self, other: ColumnOperand, /) -> ColumnExpr:
194
+ return _column_binary(self, other, "and", "&")
195
+
196
+ def __or__(self, other: ColumnOperand, /) -> ColumnExpr:
197
+ return _column_binary(self, other, "or", "|")
198
+
199
+ def __ror__(self, other: ColumnOperand, /) -> ColumnExpr:
200
+ return _column_binary(self, other, "or", "|")
201
+
202
+ def __invert__(self, /) -> ColumnExpr:
203
+ return ColumnExpr(build("not", (self._node,), {}))
204
+
205
+
206
+ def _reflected_column(
207
+ left: ColumnExpr, right: object, primitive: str, operator: str, /
208
+ ) -> ColumnExpr:
209
+ """Build the reflected non-commutative operators ``right <op> left``."""
210
+
211
+ return ColumnExpr(
212
+ build(
213
+ primitive,
214
+ (
215
+ _column_operand(right, operator),
216
+ _column_operand(left, operator),
217
+ ),
218
+ {},
219
+ )
220
+ )
221
+
222
+
223
+ @dataclass(frozen=True, slots=True, eq=False, repr=False)
224
+ class ArrayExpr(Expr[object]):
225
+ """An Array API-backed expression with backend, dtype, and lineage."""
226
+
227
+ def __eq__(self, other: ArrayOperand, /) -> ArrayExpr:
228
+ return _array_binary(self, other, "eq", "==")
229
+
230
+ def __ne__(self, other: ArrayOperand, /) -> ArrayExpr:
231
+ return _array_binary(self, other, "ne", "!=")
232
+
233
+ def __lt__(self, other: ArrayOperand, /) -> ArrayExpr:
234
+ return _array_binary(self, other, "lt", "<")
235
+
236
+ def __le__(self, other: ArrayOperand, /) -> ArrayExpr:
237
+ return _array_binary(self, other, "le", "<=")
238
+
239
+ def __gt__(self, other: ArrayOperand, /) -> ArrayExpr:
240
+ return _array_binary(self, other, "gt", ">")
241
+
242
+ def __ge__(self, other: ArrayOperand, /) -> ArrayExpr:
243
+ return _array_binary(self, other, "ge", ">=")
244
+
245
+ def __add__(self, other: ArrayOperand, /) -> ArrayExpr:
246
+ return _array_binary(self, other, "add", "+")
247
+
248
+ def __radd__(self, other: ArrayOperand, /) -> ArrayExpr:
249
+ return _array_binary(self, other, "add", "+")
250
+
251
+ def __sub__(self, other: ArrayOperand, /) -> ArrayExpr:
252
+ return _array_binary(self, other, "sub", "-")
253
+
254
+ def __rsub__(self, other: ArrayOperand, /) -> ArrayExpr:
255
+ return _reflected_array(self, other, "sub", "-")
256
+
257
+ def __mul__(self, other: ArrayOperand, /) -> ArrayExpr:
258
+ return _array_binary(self, other, "mul", "*")
259
+
260
+ def __rmul__(self, other: ArrayOperand, /) -> ArrayExpr:
261
+ return _array_binary(self, other, "mul", "*")
262
+
263
+ def __truediv__(self, other: ArrayOperand, /) -> ArrayExpr:
264
+ return _array_binary(self, other, "truediv", "/")
265
+
266
+ def __rtruediv__(self, other: ArrayOperand, /) -> ArrayExpr:
267
+ return _reflected_array(self, other, "truediv", "/")
268
+
269
+ def __neg__(self, /) -> ArrayExpr:
270
+ return ArrayExpr(build("neg", (self._node,), {}))
271
+
272
+ def __and__(self, other: ArrayOperand, /) -> ArrayExpr:
273
+ return _array_binary(self, other, "and", "&")
274
+
275
+ def __rand__(self, other: ArrayOperand, /) -> ArrayExpr:
276
+ return _array_binary(self, other, "and", "&")
277
+
278
+ def __or__(self, other: ArrayOperand, /) -> ArrayExpr:
279
+ return _array_binary(self, other, "or", "|")
280
+
281
+ def __ror__(self, other: ArrayOperand, /) -> ArrayExpr:
282
+ return _array_binary(self, other, "or", "|")
283
+
284
+ def __invert__(self, /) -> ArrayExpr:
285
+ return ArrayExpr(build("not", (self._node,), {}))
286
+
287
+
288
+ def _reflected_array(
289
+ left: ArrayExpr, right: object, primitive: str, operator: str, /
290
+ ) -> ArrayExpr:
291
+ """Build the reflected non-commutative operators ``right <op> left``."""
292
+
293
+ return ArrayExpr(
294
+ build(
295
+ primitive,
296
+ (
297
+ _array_operand(right, operator),
298
+ _array_operand(left, operator),
299
+ ),
300
+ {},
301
+ )
302
+ )
303
+
304
+
305
+ @dataclass(frozen=True, slots=True, eq=False, repr=False)
306
+ class TableExpr(Expr[object]):
307
+ """An Arrow-backed table expression."""
308
+
309
+ def __eq__(self, other: object, /) -> bool:
310
+ raise table_operator_error("==")
311
+
312
+ def __ne__(self, other: object, /) -> bool:
313
+ raise table_operator_error("!=")
314
+
315
+ def __lt__(self, other: object, /) -> bool:
316
+ raise table_operator_error("<")
317
+
318
+ def __le__(self, other: object, /) -> bool:
319
+ raise table_operator_error("<=")
320
+
321
+ def __gt__(self, other: object, /) -> bool:
322
+ raise table_operator_error(">")
323
+
324
+ def __ge__(self, other: object, /) -> bool:
325
+ raise table_operator_error(">=")
326
+
327
+ def __add__(self, other: object, /) -> bool:
328
+ raise table_operator_error("+")
329
+
330
+ def __radd__(self, other: object, /) -> bool:
331
+ raise table_operator_error("+")
332
+
333
+ def __sub__(self, other: object, /) -> bool:
334
+ raise table_operator_error("-")
335
+
336
+ def __rsub__(self, other: object, /) -> bool:
337
+ raise table_operator_error("-")
338
+
339
+ def __mul__(self, other: object, /) -> bool:
340
+ raise table_operator_error("*")
341
+
342
+ def __rmul__(self, other: object, /) -> bool:
343
+ raise table_operator_error("*")
344
+
345
+ def __truediv__(self, other: object, /) -> bool:
346
+ raise table_operator_error("/")
347
+
348
+ def __rtruediv__(self, other: object, /) -> bool:
349
+ raise table_operator_error("/")
350
+
351
+ def __neg__(self, /) -> bool:
352
+ raise table_operator_error("unary -")
353
+
354
+ def __and__(self, other: object, /) -> bool:
355
+ raise table_operator_error("&")
356
+
357
+ def __rand__(self, other: object, /) -> bool:
358
+ raise table_operator_error("&")
359
+
360
+ def __or__(self, other: object, /) -> bool:
361
+ raise table_operator_error("|")
362
+
363
+ def __ror__(self, other: object, /) -> bool:
364
+ raise table_operator_error("|")
365
+
366
+ def __invert__(self, /) -> bool:
367
+ raise table_operator_error("~")
368
+
369
+ def __getitem__(self, field: str, /) -> ColumnExpr:
370
+ if type(field) is not str:
371
+ raise TypeError(
372
+ "TableExpr field selection requires a string field name;"
373
+ f" got {type_name(field)}"
374
+ )
375
+ return ColumnExpr(build("column_ref", (self._node,), {"name": CStr(field)}))
376
+
377
+ def with_columns(self, features: FeatureSet, /) -> TableExpr:
378
+ from calc_flow.symbolic.program import FeatureSet
379
+
380
+ if not isinstance(features, FeatureSet):
381
+ raise namespace_error(
382
+ "TableExpr.with_columns", "features", "FeatureSet", features
383
+ )
384
+ names = CSeq(tuple(CStr(name) for name, _ in features.features))
385
+ return TableExpr(
386
+ build(
387
+ "with_columns",
388
+ (self._node, *(value._node for _, value in features.features)),
389
+ {"names": names},
390
+ )
391
+ )
392
+
393
+
394
+ @dataclass(frozen=True, slots=True, eq=False, repr=False)
395
+ class Parameter[T](Expr[T]):
396
+ """A named external static input of table or array kind.
397
+
398
+ Parameters define no scalar operator dunders; an array-kind parameter
399
+ participates in expressions only where a signature names it.
400
+ """
401
+
402
+ @property
403
+ def name(self) -> str:
404
+ value = self._node.attr("name")
405
+ if isinstance(value, CStr):
406
+ return value.value
407
+ raise TypeError("parameter node is missing its name attribute")
408
+
409
+ @property
410
+ def kind(self) -> BatchKind:
411
+ value = self._node.attr("kind")
412
+ if isinstance(value, CEnum):
413
+ return value.variant # type: ignore[return-value]
414
+ raise TypeError("parameter node is missing its kind attribute")
415
+
416
+
417
+ def _validate_schema(schema: Sequence[Field], path: str, /) -> tuple[Field, ...]:
418
+ fields: list[Field] = []
419
+ seen: set[str] = set()
420
+ for index, field in enumerate(schema):
421
+ if not isinstance(field, Field):
422
+ raise TypeError(
423
+ f"{path}[{index}]: schema entries must be Field values; got"
424
+ f" {type_name(field)}"
425
+ )
426
+ check_table_field_type(field.data_type, f"{path}[{index}].data_type")
427
+ if field.name in seen:
428
+ raise ValueError(
429
+ f"{path}[{index}].name: duplicate_name: duplicate field"
430
+ f" name {field.name!r}"
431
+ )
432
+ seen.add(field.name)
433
+ fields.append(field)
434
+ return tuple(fields)
435
+
436
+
437
+ def _validate_name_sequence(values: object, path: str, /) -> tuple[str, ...]:
438
+ if isinstance(values, (str, bytes)) or not isinstance(values, Sequence):
439
+ raise TypeError(
440
+ f"{path} must be a sequence of strings; got {type_name(values)}"
441
+ )
442
+ names: list[str] = []
443
+ for index, value in enumerate(values):
444
+ names.append(require_non_empty_str(value, f"{path}[{index}]"))
445
+ return tuple(names)
446
+
447
+
448
+ def _cv_schema(fields: Sequence[Field], /) -> CSeq:
449
+ return CSeq(
450
+ tuple(
451
+ CMap.from_mapping(
452
+ {
453
+ "name": CStr(field.name),
454
+ "data_type": CDType(field.data_type),
455
+ "nullable": CBool(field.nullable),
456
+ }
457
+ )
458
+ for field in fields
459
+ )
460
+ )
461
+
462
+
463
+ def table_input(
464
+ name: str,
465
+ /,
466
+ *,
467
+ schema: Sequence[Field],
468
+ entity_by: Sequence[str] = (),
469
+ event_time: str | None = None,
470
+ sequence_by: Sequence[str] = (),
471
+ ) -> TableExpr:
472
+ """Declare one named table input with its exact schema and ordering."""
473
+
474
+ if type(name) is not str:
475
+ raise TypeError(
476
+ f"inputs.table_input.name must be a non-empty string; got {type_name(name)}"
477
+ )
478
+ if not name:
479
+ raise ValueError(
480
+ "inputs.table_input.name: invalid_literal: must be a non-empty string"
481
+ )
482
+ base = f"inputs.{name}"
483
+ fields = _validate_schema(schema, f"{base}.schema")
484
+ entity = _validate_name_sequence(entity_by, f"{base}.entity_by")
485
+ sequence = _validate_name_sequence(sequence_by, f"{base}.sequence_by")
486
+ if event_time is not None:
487
+ require_non_empty_str(event_time, f"{base}.event_time")
488
+ node = build(
489
+ "table_input",
490
+ (),
491
+ {
492
+ "name": CStr(name),
493
+ "schema": _cv_schema(fields),
494
+ "entity_by": CSeq(tuple(CStr(value) for value in entity)),
495
+ "event_time": CNull() if event_time is None else CStr(event_time),
496
+ "sequence_by": CSeq(tuple(CStr(value) for value in sequence)),
497
+ },
498
+ )
499
+ return TableExpr(node)
500
+
501
+
502
+ def _is_absent_or_empty(value: object, /) -> bool:
503
+ """Type-safe absence check that never triggers element-wise equality.
504
+
505
+ Only ``None`` and a genuinely empty ``list``/``tuple`` count as absent;
506
+ array-like values (for example NumPy arrays) fail without invoking
507
+ their overloaded ``==`` so the stable rejection path is reached.
508
+ """
509
+
510
+ return value is None or (isinstance(value, (list, tuple)) and len(value) == 0)
511
+
512
+
513
+ @overload
514
+ def parameter(
515
+ name: str,
516
+ /,
517
+ *,
518
+ kind: Literal["table"],
519
+ schema: Sequence[Field],
520
+ backend: None = None,
521
+ dtype: None = None,
522
+ shape: Sequence[int] = (),
523
+ mutability: Literal["static"] = "static",
524
+ ) -> Parameter[TableExpr]: ...
525
+
526
+
527
+ @overload
528
+ def parameter(
529
+ name: str,
530
+ /,
531
+ *,
532
+ kind: Literal["array"],
533
+ schema: Sequence[Field] = (),
534
+ backend: str,
535
+ dtype: str,
536
+ shape: Sequence[int],
537
+ mutability: Literal["static"] = "static",
538
+ ) -> Parameter[ArrayExpr]: ...
539
+
540
+
541
+ def _require_parameter_name(name: object, /) -> str:
542
+ if type(name) is not str:
543
+ raise TypeError(
544
+ "static_inputs.parameter.name must be a non-empty string;"
545
+ f" got {type_name(name)}"
546
+ )
547
+ if not name:
548
+ raise ValueError(
549
+ "static_inputs.parameter.name: invalid_literal: must be a non-empty string"
550
+ )
551
+ return name
552
+
553
+
554
+ def _require_parameter_mutability(mutability: object, base: str, /) -> None:
555
+ if type(mutability) is not str:
556
+ raise TypeError(
557
+ f"{base}.mutability must be a string; got {type_name(mutability)}"
558
+ )
559
+ if mutability != "static":
560
+ raise ValueError(
561
+ f"{base}.mutability: invalid_literal: the only accepted"
562
+ " mutability is 'static'"
563
+ )
564
+
565
+
566
+ def _table_parameter(
567
+ name: str,
568
+ base: str,
569
+ schema: object,
570
+ backend: object,
571
+ dtype: object,
572
+ shape: object,
573
+ /,
574
+ ) -> Parameter[object]:
575
+ for field_name, value in (("backend", backend), ("dtype", dtype)):
576
+ if value is not None:
577
+ raise ValueError(
578
+ f"{base}.{field_name}: invalid_literal: table"
579
+ f" parameters do not accept {field_name}"
580
+ )
581
+ if not _is_absent_or_empty(shape):
582
+ raise ValueError(
583
+ f"{base}.shape: invalid_literal: table parameters do not accept shape"
584
+ )
585
+ fields = _validate_schema(schema, f"{base}.schema") # type: ignore[arg-type]
586
+ node = build(
587
+ "parameter",
588
+ (),
589
+ {
590
+ "name": CStr(name),
591
+ "kind": CEnum("batch_kind", "table"),
592
+ "mutability": CEnum("mutability", "static"),
593
+ "schema": _cv_schema(fields),
594
+ },
595
+ )
596
+ return Parameter(node)
597
+
598
+
599
+ def _array_parameter(
600
+ name: str,
601
+ base: str,
602
+ schema: object,
603
+ backend: object,
604
+ dtype: object,
605
+ shape: object,
606
+ /,
607
+ ) -> Parameter[object]:
608
+ if not _is_absent_or_empty(schema):
609
+ raise ValueError(
610
+ f"{base}.schema: invalid_literal: array parameters do not accept schema"
611
+ )
612
+ backend_value = require_non_empty_str(backend, f"{base}.backend")
613
+ dtype_value = require_non_empty_str(dtype, f"{base}.dtype")
614
+ check_array_dtype(dtype_value, f"{base}.dtype")
615
+ if isinstance(shape, (str, bytes)) or not isinstance(shape, Sequence):
616
+ raise TypeError(
617
+ f"{base}.shape must be a sequence of integers; got {type_name(shape)}"
618
+ )
619
+ dims = tuple(
620
+ require_non_negative_int(value, f"{base}.shape[{index}]")
621
+ for index, value in enumerate(shape)
622
+ )
623
+ node = build(
624
+ "parameter",
625
+ (),
626
+ {
627
+ "name": CStr(name),
628
+ "kind": CEnum("batch_kind", "array"),
629
+ "mutability": CEnum("mutability", "static"),
630
+ "backend": CStr(backend_value),
631
+ "dtype": CDType(dtype_value),
632
+ "shape": CSeq(tuple(CInt(value) for value in dims)),
633
+ },
634
+ )
635
+ return Parameter(node)
636
+
637
+
638
+ def parameter(
639
+ name: str,
640
+ /,
641
+ *,
642
+ kind: object,
643
+ schema: object = (),
644
+ backend: object = None,
645
+ dtype: object = None,
646
+ shape: object = (),
647
+ mutability: object = "static",
648
+ ) -> Parameter[object]:
649
+ """Declare one named static external parameter of table or array kind."""
650
+
651
+ validated_name = _require_parameter_name(name)
652
+ base = f"static_inputs.{validated_name}"
653
+ if type(kind) is not str or kind not in ("table", "array"):
654
+ if type(kind) is not str:
655
+ raise TypeError(f"{base}.kind must be a string; got {type_name(kind)}")
656
+ raise ValueError(
657
+ f"{base}.kind: invalid_literal: kind must be 'table' or 'array'"
658
+ )
659
+ _require_parameter_mutability(mutability, base)
660
+ if kind == "table":
661
+ return _table_parameter(validated_name, base, schema, backend, dtype, shape)
662
+ return _array_parameter(validated_name, base, schema, backend, dtype, shape)
@@ -0,0 +1,31 @@
1
+ """Deterministic lowering of symbolic programs to strict project-v3.
2
+
3
+ The lowerer resolves each declared table output into one fused row-local
4
+ segment, renders the segment as DataFusion SQL inside strict project-v3
5
+ ``expression`` nodes, and hands the document to the existing Rust graph
6
+ compiler for final port, schema, topology, and fingerprint validation. No data
7
+ object, source, sink, or runner is accepted here, and no symbolic Python runs
8
+ while a compiled plan executes.
9
+ """
10
+
11
+ from calc_flow.symbolic.lower import planners, program, segments, strategies
12
+ from calc_flow.symbolic.lower.program import (
13
+ compile_program_batch,
14
+ compile_program_stream,
15
+ lower_program_document,
16
+ )
17
+
18
+ # The package preserves the flat ``symbolic/lower.py`` namespace: every
19
+ # module-level name of the split modules stays importable from here, including
20
+ # the private helpers the lowering tests exercise.
21
+ for _module in (segments, planners, strategies, program):
22
+ for _name in dir(_module):
23
+ if not _name.startswith("__"):
24
+ globals().setdefault(_name, getattr(_module, _name))
25
+ del _module, _name
26
+
27
+ __all__ = [
28
+ "compile_program_batch",
29
+ "compile_program_stream",
30
+ "lower_program_document",
31
+ ]