593 lines
23 KiB
Plaintext
593 lines
23 KiB
Plaintext
// RelAlgIR: String Diagram IR for Relational Algebra
|
||
//
|
||
// Query plans are instances of this theory. The string diagram structure:
|
||
// - Wire elements are edges (carrying typed data streams)
|
||
// - Op elements are boxes (transforming data)
|
||
// - Composition is implicit via wire sharing (same Wire as output of one Op and input of another)
|
||
// - Cycles are allowed; well-formedness axioms ensure they contain delays
|
||
//
|
||
// See loose_thoughts/2026-01-19_19:45_relalg_ir_design.md for full design.
|
||
//
|
||
// This theory extends GeologMeta to get Srt, Func, Elem, etc.
|
||
// References use qualified names: GeologMeta/Srt, GeologMeta/Func, etc.
|
||
|
||
theory RelAlgIR extends GeologMeta {
|
||
|
||
// ============================================================
|
||
// SCHEMAS (types of data on wires)
|
||
// ============================================================
|
||
// Schemas describe the "shape" of tuples flowing on a wire.
|
||
// They mirror DSort but are specific to the relational algebra context.
|
||
|
||
Schema : Sort;
|
||
|
||
// Unit schema: empty tuple (for sources with no input)
|
||
UnitSchema : Sort;
|
||
UnitSchema/schema : UnitSchema -> Schema;
|
||
|
||
// Base schema: single column of a given sort
|
||
BaseSchema : Sort;
|
||
BaseSchema/schema : BaseSchema -> Schema;
|
||
BaseSchema/srt : BaseSchema -> GeologMeta/Srt;
|
||
|
||
// Product schema: S ⊗ T (concatenation of columns)
|
||
ProdSchema : Sort;
|
||
ProdSchema/schema : ProdSchema -> Schema;
|
||
ProdSchema/left : ProdSchema -> Schema;
|
||
ProdSchema/right : ProdSchema -> Schema;
|
||
|
||
// ============================================================
|
||
// WIRES (edges in the string diagram)
|
||
// ============================================================
|
||
// Wires are first-class citizens. Each wire carries a stream of
|
||
// tuples with a given schema. Composition is encoded by the same
|
||
// Wire appearing as output of one Op and input of another.
|
||
|
||
Wire : Sort;
|
||
Wire/schema : Wire -> Schema;
|
||
|
||
// ============================================================
|
||
// OPERATIONS (boxes in the string diagram)
|
||
// ============================================================
|
||
|
||
Op : Sort;
|
||
|
||
// ------------------------------------------------------------
|
||
// Sources (no input wires)
|
||
// ------------------------------------------------------------
|
||
|
||
// Scan: emit all elements of a sort
|
||
// () → BaseSchema(srt)
|
||
ScanOp : Sort;
|
||
ScanOp/op : ScanOp -> Op;
|
||
ScanOp/srt : ScanOp -> GeologMeta/Srt;
|
||
ScanOp/out : ScanOp -> Wire;
|
||
|
||
// Constant: emit a single known element
|
||
// () → BaseSchema(elem's sort)
|
||
ConstOp : Sort;
|
||
ConstOp/op : ConstOp -> Op;
|
||
ConstOp/elem : ConstOp -> GeologMeta/Elem;
|
||
ConstOp/out : ConstOp -> Wire;
|
||
|
||
// Empty: emit nothing (identity for union)
|
||
// () → S
|
||
EmptyOp : Sort;
|
||
EmptyOp/op : EmptyOp -> Op;
|
||
EmptyOp/out : EmptyOp -> Wire;
|
||
|
||
// ------------------------------------------------------------
|
||
// Unary operations (one input wire, one output wire)
|
||
// ------------------------------------------------------------
|
||
|
||
// Filter: keep tuples satisfying a predicate
|
||
// S → S
|
||
FilterOp : Sort;
|
||
FilterOp/op : FilterOp -> Op;
|
||
FilterOp/in : FilterOp -> Wire;
|
||
FilterOp/out : FilterOp -> Wire;
|
||
FilterOp/pred : FilterOp -> Pred;
|
||
|
||
// Project: select and reorder columns
|
||
// S → T
|
||
ProjectOp : Sort;
|
||
ProjectOp/op : ProjectOp -> Op;
|
||
ProjectOp/in : ProjectOp -> Wire;
|
||
ProjectOp/out : ProjectOp -> Wire;
|
||
ProjectOp/mapping : ProjectOp -> ProjMapping;
|
||
|
||
// Distinct: deduplicate tuples (collapse multiplicities to 0/1)
|
||
// S → S
|
||
DistinctOp : Sort;
|
||
DistinctOp/op : DistinctOp -> Op;
|
||
DistinctOp/in : DistinctOp -> Wire;
|
||
DistinctOp/out : DistinctOp -> Wire;
|
||
|
||
// Negate: flip multiplicities (for computing differences)
|
||
// S → S
|
||
NegateOp : Sort;
|
||
NegateOp/op : NegateOp -> Op;
|
||
NegateOp/in : NegateOp -> Wire;
|
||
NegateOp/out : NegateOp -> Wire;
|
||
|
||
// Apply function: add a column by applying a function
|
||
// S → S ⊗ BaseSchema(cod)
|
||
ApplyOp : Sort;
|
||
ApplyOp/op : ApplyOp -> Op;
|
||
ApplyOp/in : ApplyOp -> Wire;
|
||
ApplyOp/out : ApplyOp -> Wire;
|
||
ApplyOp/func : ApplyOp -> GeologMeta/Func;
|
||
ApplyOp/arg_col : ApplyOp -> ColRef;
|
||
|
||
// ------------------------------------------------------------
|
||
// Binary operations (two input wires, one output wire)
|
||
// ------------------------------------------------------------
|
||
|
||
// Join: combine tuples from two sources where condition holds
|
||
// S × T → S ⊗ T (filtered)
|
||
JoinOp : Sort;
|
||
JoinOp/op : JoinOp -> Op;
|
||
JoinOp/left_in : JoinOp -> Wire;
|
||
JoinOp/right_in : JoinOp -> Wire;
|
||
JoinOp/out : JoinOp -> Wire;
|
||
JoinOp/cond : JoinOp -> JoinCond;
|
||
|
||
// Union: combine tuples from two sources (Z-set addition)
|
||
// S × S → S
|
||
UnionOp : Sort;
|
||
UnionOp/op : UnionOp -> Op;
|
||
UnionOp/left_in : UnionOp -> Wire;
|
||
UnionOp/right_in : UnionOp -> Wire;
|
||
UnionOp/out : UnionOp -> Wire;
|
||
|
||
// ------------------------------------------------------------
|
||
// DBSP Temporal Operators
|
||
// ------------------------------------------------------------
|
||
// These operate on streams over discrete time.
|
||
// They are essential for incremental computation and feedback loops.
|
||
|
||
// Delay: z⁻¹, output at time t is input at time t-1
|
||
// S → S
|
||
// IMPORTANT: Delays break instantaneous cycles, making feedback well-founded.
|
||
DelayOp : Sort;
|
||
DelayOp/op : DelayOp -> Op;
|
||
DelayOp/in : DelayOp -> Wire;
|
||
DelayOp/out : DelayOp -> Wire;
|
||
|
||
// Differentiate: δ = 1 - z⁻¹, compute changes since last timestep
|
||
// S → S (output is the delta/diff of input)
|
||
DiffOp : Sort;
|
||
DiffOp/op : DiffOp -> Op;
|
||
DiffOp/in : DiffOp -> Wire;
|
||
DiffOp/out : DiffOp -> Wire;
|
||
|
||
// Integrate: ∫ = Σ, accumulate all inputs over time
|
||
// S → S (output is running sum of all inputs)
|
||
// NOTE: Has implicit delay semantics, also breaks instantaneous cycles.
|
||
IntegrateOp : Sort;
|
||
IntegrateOp/op : IntegrateOp -> Op;
|
||
IntegrateOp/in : IntegrateOp -> Wire;
|
||
IntegrateOp/out : IntegrateOp -> Wire;
|
||
|
||
// ============================================================
|
||
// PREDICATES (for filter conditions)
|
||
// ============================================================
|
||
|
||
Pred : Sort;
|
||
|
||
// True: always satisfied
|
||
TruePred : Sort;
|
||
TruePred/pred : TruePred -> Pred;
|
||
|
||
// False: never satisfied
|
||
FalsePred : Sort;
|
||
FalsePred/pred : FalsePred -> Pred;
|
||
|
||
// Column equality: col_i = col_j
|
||
ColEqPred : Sort;
|
||
ColEqPred/pred : ColEqPred -> Pred;
|
||
ColEqPred/left : ColEqPred -> ColRef;
|
||
ColEqPred/right : ColEqPred -> ColRef;
|
||
|
||
// Constant equality: col = constant element
|
||
ConstEqPred : Sort;
|
||
ConstEqPred/pred : ConstEqPred -> Pred;
|
||
ConstEqPred/col : ConstEqPred -> ColRef;
|
||
ConstEqPred/val : ConstEqPred -> GeologMeta/Elem;
|
||
|
||
// Function result equality: f(col_arg) = col_result
|
||
FuncEqPred : Sort;
|
||
FuncEqPred/pred : FuncEqPred -> Pred;
|
||
FuncEqPred/func : FuncEqPred -> GeologMeta/Func;
|
||
FuncEqPred/arg : FuncEqPred -> ColRef;
|
||
FuncEqPred/result : FuncEqPred -> ColRef;
|
||
|
||
// Function result equals constant: f(col_arg) = expected_elem
|
||
FuncConstEqPred : Sort;
|
||
FuncConstEqPred/pred : FuncConstEqPred -> Pred;
|
||
FuncConstEqPred/func : FuncConstEqPred -> GeologMeta/Func;
|
||
FuncConstEqPred/arg : FuncConstEqPred -> ColRef;
|
||
FuncConstEqPred/expected : FuncConstEqPred -> GeologMeta/Elem;
|
||
|
||
// Conjunction: p ∧ q
|
||
AndPred : Sort;
|
||
AndPred/pred : AndPred -> Pred;
|
||
AndPred/left : AndPred -> Pred;
|
||
AndPred/right : AndPred -> Pred;
|
||
|
||
// Disjunction: p ∨ q
|
||
OrPred : Sort;
|
||
OrPred/pred : OrPred -> Pred;
|
||
OrPred/left : OrPred -> Pred;
|
||
OrPred/right : OrPred -> Pred;
|
||
|
||
// ============================================================
|
||
// JOIN CONDITIONS
|
||
// ============================================================
|
||
|
||
JoinCond : Sort;
|
||
|
||
// Equijoin: left.col_i = right.col_j
|
||
EquiJoinCond : Sort;
|
||
EquiJoinCond/cond : EquiJoinCond -> JoinCond;
|
||
EquiJoinCond/left_col : EquiJoinCond -> ColRef;
|
||
EquiJoinCond/right_col : EquiJoinCond -> ColRef;
|
||
|
||
// Cross join: cartesian product (no condition)
|
||
CrossJoinCond : Sort;
|
||
CrossJoinCond/cond : CrossJoinCond -> JoinCond;
|
||
|
||
// General predicate join
|
||
PredJoinCond : Sort;
|
||
PredJoinCond/cond : PredJoinCond -> JoinCond;
|
||
PredJoinCond/pred : PredJoinCond -> Pred;
|
||
|
||
// ============================================================
|
||
// COLUMN REFERENCES
|
||
// ============================================================
|
||
// References to specific columns within a schema.
|
||
// Used in predicates and projections.
|
||
|
||
ColRef : Sort;
|
||
ColRef/wire : ColRef -> Wire; // which wire's schema we're referencing
|
||
ColRef/path : ColRef -> ColPath; // path into the schema
|
||
|
||
// Column path: navigate into nested product schemas
|
||
ColPath : Sort;
|
||
|
||
// Here: we're at the target
|
||
HerePath : Sort;
|
||
HerePath/path : HerePath -> ColPath;
|
||
|
||
// Left: descend into left of product
|
||
LeftPath : Sort;
|
||
LeftPath/path : LeftPath -> ColPath;
|
||
LeftPath/rest : LeftPath -> ColPath;
|
||
|
||
// Right: descend into right of product
|
||
RightPath : Sort;
|
||
RightPath/path : RightPath -> ColPath;
|
||
RightPath/rest : RightPath -> ColPath;
|
||
|
||
// ============================================================
|
||
// PROJECTION MAPPINGS
|
||
// ============================================================
|
||
// Specifies how to construct output columns from input columns.
|
||
|
||
ProjMapping : Sort;
|
||
|
||
// Projection entries (which input columns become which output columns)
|
||
ProjEntry : Sort;
|
||
ProjEntry/mapping : ProjEntry -> ProjMapping;
|
||
ProjEntry/source : ProjEntry -> ColRef;
|
||
ProjEntry/target_path : ProjEntry -> ColPath;
|
||
|
||
// ============================================================
|
||
// REACHABILITY RELATIONS (for cycle analysis)
|
||
// ============================================================
|
||
|
||
// w1 reaches w2 via some path through operations
|
||
reaches : [from: Wire, to: Wire] -> Prop;
|
||
|
||
// Reachability through each operation type
|
||
ax/reaches/scan : forall s : ScanOp, w : Wire.
|
||
s ScanOp/out = w |- [from: w, to: w] reaches; // trivial self-reach for source
|
||
|
||
ax/reaches/filter : forall f : FilterOp, w1 : Wire, w2 : Wire.
|
||
f FilterOp/in = w1, f FilterOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/project : forall p : ProjectOp, w1 : Wire, w2 : Wire.
|
||
p ProjectOp/in = w1, p ProjectOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/distinct : forall d : DistinctOp, w1 : Wire, w2 : Wire.
|
||
d DistinctOp/in = w1, d DistinctOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/negate : forall n : NegateOp, w1 : Wire, w2 : Wire.
|
||
n NegateOp/in = w1, n NegateOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/apply : forall a : ApplyOp, w1 : Wire, w2 : Wire.
|
||
a ApplyOp/in = w1, a ApplyOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/join/left : forall j : JoinOp, w1 : Wire, w2 : Wire.
|
||
j JoinOp/left_in = w1, j JoinOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/join/right : forall j : JoinOp, w1 : Wire, w2 : Wire.
|
||
j JoinOp/right_in = w1, j JoinOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/union/left : forall u : UnionOp, w1 : Wire, w2 : Wire.
|
||
u UnionOp/left_in = w1, u UnionOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/union/right : forall u : UnionOp, w1 : Wire, w2 : Wire.
|
||
u UnionOp/right_in = w1, u UnionOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/delay : forall d : DelayOp, w1 : Wire, w2 : Wire.
|
||
d DelayOp/in = w1, d DelayOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/diff : forall d : DiffOp, w1 : Wire, w2 : Wire.
|
||
d DiffOp/in = w1, d DiffOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
ax/reaches/integrate : forall i : IntegrateOp, w1 : Wire, w2 : Wire.
|
||
i IntegrateOp/in = w1, i IntegrateOp/out = w2 |- [from: w1, to: w2] reaches;
|
||
|
||
// Transitive closure
|
||
ax/reaches/trans : forall w1 : Wire, w2 : Wire, w3 : Wire.
|
||
[from: w1, to: w2] reaches, [from: w2, to: w3] reaches |-
|
||
[from: w1, to: w3] reaches;
|
||
|
||
// ============================================================
|
||
// INSTANTANEOUS REACHABILITY (paths without delay)
|
||
// ============================================================
|
||
// This relation tracks paths that do NOT go through DelayOp or IntegrateOp.
|
||
// Used to detect "bad" feedback loops that would require instantaneous computation.
|
||
|
||
reaches_instant : [from: Wire, to: Wire] -> Prop;
|
||
|
||
// Same axioms as reaches, EXCEPT for DelayOp and IntegrateOp
|
||
ax/reaches_instant/filter : forall f : FilterOp, w1 : Wire, w2 : Wire.
|
||
f FilterOp/in = w1, f FilterOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
||
|
||
ax/reaches_instant/project : forall p : ProjectOp, w1 : Wire, w2 : Wire.
|
||
p ProjectOp/in = w1, p ProjectOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
||
|
||
ax/reaches_instant/distinct : forall d : DistinctOp, w1 : Wire, w2 : Wire.
|
||
d DistinctOp/in = w1, d DistinctOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
||
|
||
ax/reaches_instant/negate : forall n : NegateOp, w1 : Wire, w2 : Wire.
|
||
n NegateOp/in = w1, n NegateOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
||
|
||
ax/reaches_instant/apply : forall a : ApplyOp, w1 : Wire, w2 : Wire.
|
||
a ApplyOp/in = w1, a ApplyOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
||
|
||
ax/reaches_instant/join/left : forall j : JoinOp, w1 : Wire, w2 : Wire.
|
||
j JoinOp/left_in = w1, j JoinOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
||
|
||
ax/reaches_instant/join/right : forall j : JoinOp, w1 : Wire, w2 : Wire.
|
||
j JoinOp/right_in = w1, j JoinOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
||
|
||
ax/reaches_instant/union/left : forall u : UnionOp, w1 : Wire, w2 : Wire.
|
||
u UnionOp/left_in = w1, u UnionOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
||
|
||
ax/reaches_instant/union/right : forall u : UnionOp, w1 : Wire, w2 : Wire.
|
||
u UnionOp/right_in = w1, u UnionOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
||
|
||
// NOTE: No axioms for DelayOp or IntegrateOp!
|
||
// They break instantaneous reachability.
|
||
|
||
// DiffOp is instantaneous (it uses delay internally but outputs immediately)
|
||
ax/reaches_instant/diff : forall d : DiffOp, w1 : Wire, w2 : Wire.
|
||
d DiffOp/in = w1, d DiffOp/out = w2 |- [from: w1, to: w2] reaches_instant;
|
||
|
||
// Transitive closure
|
||
ax/reaches_instant/trans : forall w1 : Wire, w2 : Wire, w3 : Wire.
|
||
[from: w1, to: w2] reaches_instant, [from: w2, to: w3] reaches_instant |-
|
||
[from: w1, to: w3] reaches_instant;
|
||
|
||
// ============================================================
|
||
// WELL-FORMEDNESS: NO INSTANTANEOUS CYCLES
|
||
// ============================================================
|
||
// Every cycle must contain at least one DelayOp or IntegrateOp.
|
||
// This ensures feedback loops are computable via iteration.
|
||
|
||
ax/wf/no_instant_cycle : forall w : Wire.
|
||
[from: w, to: w] reaches_instant |- false;
|
||
|
||
// ============================================================
|
||
// WELL-FORMEDNESS: SCHEMA CONSISTENCY
|
||
// ============================================================
|
||
// Operations must connect wires with compatible schemas.
|
||
|
||
// Filter preserves schema
|
||
ax/wf/filter_schema : forall f : FilterOp, w1 : Wire, w2 : Wire.
|
||
f FilterOp/in = w1, f FilterOp/out = w2 |-
|
||
w1 Wire/schema = w2 Wire/schema;
|
||
|
||
// Distinct preserves schema
|
||
ax/wf/distinct_schema : forall d : DistinctOp, w1 : Wire, w2 : Wire.
|
||
d DistinctOp/in = w1, d DistinctOp/out = w2 |-
|
||
w1 Wire/schema = w2 Wire/schema;
|
||
|
||
// Negate preserves schema
|
||
ax/wf/negate_schema : forall n : NegateOp, w1 : Wire, w2 : Wire.
|
||
n NegateOp/in = w1, n NegateOp/out = w2 |-
|
||
w1 Wire/schema = w2 Wire/schema;
|
||
|
||
// Delay preserves schema
|
||
ax/wf/delay_schema : forall d : DelayOp, w1 : Wire, w2 : Wire.
|
||
d DelayOp/in = w1, d DelayOp/out = w2 |-
|
||
w1 Wire/schema = w2 Wire/schema;
|
||
|
||
// Diff preserves schema
|
||
ax/wf/diff_schema : forall d : DiffOp, w1 : Wire, w2 : Wire.
|
||
d DiffOp/in = w1, d DiffOp/out = w2 |-
|
||
w1 Wire/schema = w2 Wire/schema;
|
||
|
||
// Integrate preserves schema
|
||
ax/wf/integrate_schema : forall i : IntegrateOp, w1 : Wire, w2 : Wire.
|
||
i IntegrateOp/in = w1, i IntegrateOp/out = w2 |-
|
||
w1 Wire/schema = w2 Wire/schema;
|
||
|
||
// Union requires same schema on both inputs
|
||
ax/wf/union_schema_left : forall u : UnionOp, wl : Wire, wr : Wire, wo : Wire.
|
||
u UnionOp/left_in = wl, u UnionOp/right_in = wr, u UnionOp/out = wo |-
|
||
wl Wire/schema = wo Wire/schema;
|
||
|
||
ax/wf/union_schema_right : forall u : UnionOp, wl : Wire, wr : Wire, wo : Wire.
|
||
u UnionOp/left_in = wl, u UnionOp/right_in = wr, u UnionOp/out = wo |-
|
||
wr Wire/schema = wo Wire/schema;
|
||
|
||
// Scan output schema must be BaseSchema of the scanned sort
|
||
// (This requires existential in conclusion, which geometric logic supports)
|
||
ax/wf/scan_schema : forall s : ScanOp, srt : GeologMeta/Srt, w : Wire.
|
||
s ScanOp/srt = srt, s ScanOp/out = w |-
|
||
exists bs : BaseSchema. bs BaseSchema/srt = srt, w Wire/schema = bs BaseSchema/schema;
|
||
|
||
// Join output schema is product of input schemas
|
||
ax/wf/join_schema : forall j : JoinOp, wl : Wire, wr : Wire, wo : Wire.
|
||
j JoinOp/left_in = wl, j JoinOp/right_in = wr, j JoinOp/out = wo |-
|
||
exists ps : ProdSchema.
|
||
ps ProdSchema/left = wl Wire/schema,
|
||
ps ProdSchema/right = wr Wire/schema,
|
||
wo Wire/schema = ps ProdSchema/schema;
|
||
|
||
// ============================================================
|
||
// ALGEBRAIC LAWS (for query optimization)
|
||
// ============================================================
|
||
// These axioms express equivalences between query plans.
|
||
// An optimizer uses these to transform plans into more efficient forms.
|
||
//
|
||
// Notation: We describe semantic equivalence between operators.
|
||
// In practice, equivalence means the output wire produces the same Z-set.
|
||
//
|
||
// These are stated as properties (Prop-valued relations) rather than
|
||
// equational axioms, since geolog's geometric logic doesn't have
|
||
// built-in equality on terms. The optimizer interprets these as rewrite rules.
|
||
|
||
// Wire equivalence: two wires produce the same Z-set
|
||
equiv : [a: Wire, b: Wire] -> Prop;
|
||
|
||
// Reflexivity
|
||
ax/equiv/refl : forall w : Wire.
|
||
|- [a: w, b: w] equiv;
|
||
|
||
// Symmetry
|
||
ax/equiv/sym : forall w1 : Wire, w2 : Wire.
|
||
[a: w1, b: w2] equiv |- [a: w2, b: w1] equiv;
|
||
|
||
// Transitivity
|
||
ax/equiv/trans : forall w1 : Wire, w2 : Wire, w3 : Wire.
|
||
[a: w1, b: w2] equiv, [a: w2, b: w3] equiv |- [a: w1, b: w3] equiv;
|
||
|
||
// ------------------------------------------------------------
|
||
// Filter Laws
|
||
// ------------------------------------------------------------
|
||
|
||
// Filter(True, x) ≡ x
|
||
ax/filter_true : forall f : FilterOp, t : TruePred, wi : Wire, wo : Wire.
|
||
f FilterOp/pred = t TruePred/pred, f FilterOp/in = wi, f FilterOp/out = wo |-
|
||
[a: wo, b: wi] equiv;
|
||
|
||
// Filter(False, x) ≡ Empty
|
||
// (Every tuple is filtered out, result is empty)
|
||
// This would need EmptyOp with matching schema; omitted for simplicity.
|
||
|
||
// Filter-Filter Fusion: Filter(p, Filter(q, x)) ≡ Filter(p ∧ q, x)
|
||
// Expressed as: If f2.in = f1.out, then there exists a fused filter.
|
||
ax/filter_fusion : forall f1 : FilterOp, f2 : FilterOp,
|
||
p1 : Pred, p2 : Pred,
|
||
wi : Wire, wm : Wire, wo : Wire.
|
||
f1 FilterOp/in = wi, f1 FilterOp/out = wm, f1 FilterOp/pred = p1,
|
||
f2 FilterOp/in = wm, f2 FilterOp/out = wo, f2 FilterOp/pred = p2 |-
|
||
exists f3 : FilterOp, pa : AndPred.
|
||
pa AndPred/left = p1, pa AndPred/right = p2,
|
||
f3 FilterOp/in = wi, f3 FilterOp/pred = pa AndPred/pred,
|
||
[a: wo, b: f3 FilterOp/out] equiv;
|
||
|
||
// ------------------------------------------------------------
|
||
// Distinct Laws
|
||
// ------------------------------------------------------------
|
||
|
||
// Distinct is idempotent: Distinct(Distinct(x)) ≡ Distinct(x)
|
||
ax/distinct_idem : forall d1 : DistinctOp, d2 : DistinctOp,
|
||
wi : Wire, wm : Wire, wo : Wire.
|
||
d1 DistinctOp/in = wi, d1 DistinctOp/out = wm,
|
||
d2 DistinctOp/in = wm, d2 DistinctOp/out = wo |-
|
||
[a: wo, b: wm] equiv;
|
||
|
||
// ------------------------------------------------------------
|
||
// Union Laws
|
||
// ------------------------------------------------------------
|
||
|
||
// Union is commutative: Union(x, y) ≡ Union(y, x)
|
||
ax/union_comm : forall u1 : UnionOp, wl : Wire, wr : Wire, wo : Wire.
|
||
u1 UnionOp/left_in = wl, u1 UnionOp/right_in = wr, u1 UnionOp/out = wo |-
|
||
exists u2 : UnionOp.
|
||
u2 UnionOp/left_in = wr, u2 UnionOp/right_in = wl,
|
||
[a: wo, b: u2 UnionOp/out] equiv;
|
||
|
||
// Union is associative: Union(x, Union(y, z)) ≡ Union(Union(x, y), z)
|
||
ax/union_assoc : forall u1 : UnionOp, u2 : UnionOp,
|
||
wa : Wire, wb : Wire, wc : Wire, wyz : Wire, wo : Wire.
|
||
u2 UnionOp/left_in = wb, u2 UnionOp/right_in = wc, u2 UnionOp/out = wyz,
|
||
u1 UnionOp/left_in = wa, u1 UnionOp/right_in = wyz, u1 UnionOp/out = wo |-
|
||
exists u3 : UnionOp, u4 : UnionOp, wab : Wire.
|
||
u3 UnionOp/left_in = wa, u3 UnionOp/right_in = wb, u3 UnionOp/out = wab,
|
||
u4 UnionOp/left_in = wab, u4 UnionOp/right_in = wc,
|
||
[a: wo, b: u4 UnionOp/out] equiv;
|
||
|
||
// Union with Empty: Union(x, Empty) ≡ x
|
||
ax/union_empty_right : forall u : UnionOp, e : EmptyOp, wi : Wire, we : Wire, wo : Wire.
|
||
e EmptyOp/out = we, u UnionOp/left_in = wi, u UnionOp/right_in = we, u UnionOp/out = wo |-
|
||
[a: wo, b: wi] equiv;
|
||
|
||
ax/union_empty_left : forall u : UnionOp, e : EmptyOp, wi : Wire, we : Wire, wo : Wire.
|
||
e EmptyOp/out = we, u UnionOp/left_in = we, u UnionOp/right_in = wi, u UnionOp/out = wo |-
|
||
[a: wo, b: wi] equiv;
|
||
|
||
// ------------------------------------------------------------
|
||
// Negate Laws
|
||
// ------------------------------------------------------------
|
||
|
||
// Double negation: Negate(Negate(x)) ≡ x
|
||
ax/negate_involution : forall n1 : NegateOp, n2 : NegateOp,
|
||
wi : Wire, wm : Wire, wo : Wire.
|
||
n1 NegateOp/in = wi, n1 NegateOp/out = wm,
|
||
n2 NegateOp/in = wm, n2 NegateOp/out = wo |-
|
||
[a: wo, b: wi] equiv;
|
||
|
||
// ------------------------------------------------------------
|
||
// Join Laws
|
||
// ------------------------------------------------------------
|
||
|
||
// Cross join is commutative (up to column reordering)
|
||
// Note: The output schemas differ, so this needs a projection to swap columns.
|
||
// Omitted for now as it requires more complex schema manipulation.
|
||
|
||
// Join with Empty: Join(x, Empty) ≡ Empty
|
||
ax/join_empty_right : forall j : JoinOp, e : EmptyOp, wi : Wire, we : Wire, wo : Wire.
|
||
e EmptyOp/out = we, j JoinOp/left_in = wi, j JoinOp/right_in = we, j JoinOp/out = wo |-
|
||
exists e2 : EmptyOp. [a: wo, b: e2 EmptyOp/out] equiv;
|
||
|
||
ax/join_empty_left : forall j : JoinOp, e : EmptyOp, wi : Wire, we : Wire, wo : Wire.
|
||
e EmptyOp/out = we, j JoinOp/left_in = we, j JoinOp/right_in = wi, j JoinOp/out = wo |-
|
||
exists e2 : EmptyOp. [a: wo, b: e2 EmptyOp/out] equiv;
|
||
|
||
// ------------------------------------------------------------
|
||
// DBSP Laws
|
||
// ------------------------------------------------------------
|
||
|
||
// Differentiation is inverse of integration (for streams of changes)
|
||
// Diff(Integrate(x)) ≡ x (for Δ-streams)
|
||
// Integrate(Diff(x)) ≡ x - x₀ (up to initial value)
|
||
// These are more subtle and depend on stream semantics; omitted for now.
|
||
|
||
// Delay respects Union: z⁻¹(x ∪ y) ≡ z⁻¹(x) ∪ z⁻¹(y)
|
||
ax/delay_union : forall u : UnionOp, d : DelayOp,
|
||
wl : Wire, wr : Wire, wu : Wire, wo : Wire.
|
||
u UnionOp/left_in = wl, u UnionOp/right_in = wr, u UnionOp/out = wu,
|
||
d DelayOp/in = wu, d DelayOp/out = wo |-
|
||
exists dl : DelayOp, dr : DelayOp, u2 : UnionOp.
|
||
dl DelayOp/in = wl, dr DelayOp/in = wr,
|
||
u2 UnionOp/left_in = dl DelayOp/out, u2 UnionOp/right_in = dr DelayOp/out,
|
||
[a: wo, b: u2 UnionOp/out] equiv;
|
||
}
|