relmath-rs 0.7.0

Relation-first mathematics and scientific computing in Rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
# relmath

`relmath` is the library crate inside the `relmath-rs` repository.

It provides exact finite relations with deterministic `BTreeSet`-backed
iteration order.

## Current Surface

The current public API covers:

- `traits::FiniteRelation` and `traits::RelationView` for shared tuple-count
  inspection and deterministic read-only iteration across the exact unary,
  binary, and n-ary relation types
- `traits::ExactSupport`, `traits::ToExactUnaryRelation`,
  `traits::ToExactBinaryRelation`, and `traits::ToExactNaryRelation` for
  shared exact-support materialization across the released add-on surfaces
- `annotated::Semiring`, `annotated::BooleanSemiring`, and
  `annotated::AnnotatedRelation<F, A>` for additive annotation foundations
- `temporal::Interval<T>` and `temporal::IntervalError<T>` for deterministic
  half-open interval foundations
- `temporal::ValidTimeSupport<T>` and `temporal::ValidTimeRelation<F, T>` for
  deterministic valid-time fact support over exact relations
- `FiniteCarrier<T>` for deterministic explicit finite carriers distinct from
  support inferred from stored tuples
- `UnaryRelation<T>` for finite unary relations (sets)
- `BinaryRelation<A, B>` for finite binary relations
- `GroupedRelation<T>` for deterministic exact grouped n-ary output
- `NaryRelation<T>` for deterministic schema-aware exact n-ary relations
- `provenance::ProvenanceRelation<F, P>` and `provenance::ProvenanceSet<P>`
  for deterministic fact-level provenance tokens
- schema validation with explicit n-ary relation errors
- named-column inspection with zero-based `column_index`
- std-only named-row onboarding and export with `BTreeMap`
- union, intersection, and difference
- domain, range, converse, and composition
- domain/range restriction plus image/preimage with unary relations
- identity on a carrier
- transitive and reflexive-transitive closure on homogeneous relations
- relation property checks for reflexivity, irreflexivity, symmetry,
  antisymmetry, transitivity, equivalence, and partial order
- n-ary schema inspection, row insertion, deterministic iteration, selection,
  projection, rename, natural join, and schema-compatible set algebra

Composition uses relational order:

- `r.compose(&s)` means `r ; s`
- the result contains `(a, c)` when some `b` satisfies `(a, b) in r` and
  `(b, c) in s`

## Current Limits

This crate currently implements the exact G1 core plus the released G2
schema-aware n-ary surface, the released G3 provenance surface, the released
G4 annotated relation surface, the released G5 valid-time relation surface,
the released G6 shared-core convergence layer, and the first executable G7
carrier slices:

- natural join plus exact keyed grouping with row counts have landed so far;
  broader join families, richer aggregation, and division are still later work
- provenance currently tracks exact base-fact tokens only; base-fact `why`
  queries have landed, but derived tuple explanations, `why_not` queries, and
  derivation DAGs are still later work
- absent facts return `None` from `why` and `provenance_of`; the current API
  does not use an empty witness as an "absent explanation" sentinel
- the first rule layer is documented as a future positive finite
  least-fixed-point surface over named exact relations, but no public rules
  API has landed yet
- deterministic annotated fact storage and exact support materialization have
  landed in G4, but annotated relation algebra remains later work
- the first G5 valid-time relation foundation plus `snapshot_at` and
  relation-level `restrict_to` have landed, but temporal joins and temporal
  composition are still later work
- `RelationView` plus `ExactSupport` and the `ToExact*Relation` materialization
  traits have landed in G6, while broader common core types and typed-row or
  macro ergonomics remain planning-only
- `FiniteCarrier<T>` has landed as the first explicit carrier boundary, and
  additive carrier-aware exact operations now integrate it directly without
  changing the published `UnaryRelation<T>`-based carrier arguments
- no complement, total-relation, or public `Universe<T>` API has landed yet;
  ADR 0015 keeps that broader carrier-domain work planning-only for now
- no typed row derives or schema macros yet; G6 currently fixes only the
  adapter-first planning boundary for that later work
- no broad weighted, fuzzy, or probabilistic relation families yet
- no solver-backed or symbolic evaluation

The repository ships focused examples under `examples/`:

- `family` for ancestry and reachability
- `access_control` for role-permission propagation
- `workflow` for state reachability
- `curriculum` for schema-aware n-ary filtering and projection
- `carrier` for explicit finite carriers distinct from relation-induced support
- `provenance` for deterministic fact-token evidence tracking
- `annotated` for deterministic annotated facts with exact support queries
- `semiring` for the first additive annotation-domain foundation
- `intervals` for deterministic half-open temporal interval reasoning
- `valid_time` for deterministic valid-time fact support, snapshots, and
  restriction workflows
- `relation_view` for the first shared read-only exact-relation boundary
- `capability_boundaries` for the first shared exact-support capability layer

## N-ary Row Algebra Notes

- `select` keeps the existing schema and preserves deterministic order among
  surviving rows
- `project` follows the requested column order exactly
- `project` currently rejects empty projections and duplicate projected columns
- `rename` is a no-op when the source and target names are the same
- `union`, `intersection`, and `difference` require exact schema equality,
  including column order

## N-ary Interchange Notes

- the current G2 interchange boundary is std-only and dependency-free
- `from_named_rows` loads `BTreeMap` records into an explicit schema
- `to_named_rows` exports name-addressable `BTreeMap` records in deterministic
  row order
- missing and unexpected columns are rejected explicitly
- serde, JSON, and CSV / TSV onboarding remain later feature-gated work

## N-ary Join Notes

- `natural_join` matches rows when every shared column has equal values
- when two schemas are disjoint, `natural_join` behaves as a cartesian product
- the output schema keeps the entire left schema, then appends right-only
  columns in their original order
- output row order stays deterministic because rows are materialized into a
  `BTreeSet`
- if no rows match, the result is empty but still carries the joined schema

## N-ary Grouping Notes

- `group_by` uses explicit key columns in the requested order
- `group(key)` returns the member relation for one exact grouping key
- empty grouping keys are currently rejected by the exact core
- each member group keeps the original relation schema in this first slice
- group iteration order is deterministic by key
- `counts` is the first exact aggregate and returns the number of stored rows
  in each group after relation deduplication

## G3 Provenance Notes

- the first G3 provenance slice is additive and lives under
  `relmath::provenance`
- `ProvenanceRelation<F, P>` records which exact facts are present and which
  deterministic token set is attached to each fact
- `ProvenanceSet<P>` is the user-visible witness type returned by explanation
  queries for present stored facts
- repeated insertion of the same fact with a new token combines provenance by
  exact set union
- `why(fact)` is the preferred explanation query and returns the deterministic
  witness for a stored fact and `None` when the fact is absent
- `provenance_of(fact)` is the current alias for retrieving that same exact
  witness
- `support()`, `to_unary_relation()`, `to_binary_relation()`, and
  `to_nary_relation()` forget provenance tokens and materialize exact relation
  support only
- witnesses in this first slice are exact token sets for stored facts
- derived tuple provenance, `why_not`, and rule-driven explanations remain
  later work, although the first rule-planning ADR now fixes the intended
  least-fixed-point direction for a later implementation

## G3 Example

```rust
use relmath::provenance::ProvenanceRelation;

let evidence = ProvenanceRelation::from_facts([
    (("BRCA1", "BreastCancer"), "curated_panel"),
    (("BRCA1", "BreastCancer"), "paper_12"),
    (("TP53", "BreastCancer"), "paper_77"),
]);

let why = evidence
    .why(&("BRCA1", "BreastCancer"))
    .expect("expected explanation");

assert_eq!(why.to_vec(), vec!["curated_panel", "paper_12"]);
assert_eq!(
    evidence
        .provenance_of(&("BRCA1", "BreastCancer"))
        .expect("expected explanation")
        .to_vec(),
    vec!["curated_panel", "paper_12"]
);
assert!(evidence.why(&("BRCA1", "Olaparib")).is_none());
assert_eq!(
    evidence.support().to_vec(),
    vec![("BRCA1", "BreastCancer"), ("TP53", "BreastCancer")]
);
```

## G3 Rule Planning Notes

- the first rule and fixed-point slice is documented in ADR 0007
- the planned first rule shape is positive finite rules over named exact
  relations with least-fixed-point semantics
- no public `rules` module or executable rule engine has landed in this crate
  yet

## G4 Annotated Notes

- the first G4 annotated slice is additive and lives under
  `relmath::annotated`
- `Semiring` defines zero as absence, one as multiplicative identity, `add`
  as union-like combination, and `mul` as composition-like chaining
- `BooleanSemiring` is the first built-in annotation family and models the
  exact boolean semantics already used by the published relation core
- `AnnotatedRelation<F, A>` stores annotated facts in deterministic fact order
  and combines repeated facts by `Semiring::add`
- only non-zero annotations are stored, so zero means absence from exact
  support
- `contains_fact(fact)` and `annotation_of(fact)` therefore agree on support:
  absent or zero-annotated facts return `false` and `None`
- unlike `provenance::why`, `annotation_of(fact)` returns the stored
  annotation value itself rather than a witness set
- `support()`, `to_unary_relation()`, `to_binary_relation()`, and
  `to_nary_relation()` forget annotations and materialize only exact support
- annotated relation algebra, valid-time fact storage, and broader weighted,
  fuzzy, or probabilistic families remain later work

## G5 Valid-Time Notes

- in the current G5 terminology, an *interval* is one half-open window
  `[start, end)`, *support* is the canonical interval set attached to one
  stored fact, and *exact support* is the set of facts that remains after
  forgetting time
- `relmath::temporal::Interval<T>` remains the executable interval foundation
  for deterministic half-open `[start, end)` semantics
- `relmath::temporal::ValidTimeSupport<T>` is the canonical interval support
  attached to one fact
- `ValidTimeSupport::overlaps(interval)` reports non-empty temporal overlap,
  while `ValidTimeSupport::restrict_to(interval)` returns canonical restricted
  support and becomes empty when overlap is absent
- `relmath::temporal::ValidTimeRelation<F, T>` stores facts in deterministic
  fact order and coalesces overlapping or directly adjacent support intervals
  for the same fact
- `valid_time_of(fact)` returns the canonical valid-time support for one fact
  and `None` when the fact is absent
- `is_active_at(fact, point)` checks whether one fact is active at one time
  point using half-open boundary semantics
- `snapshot_at(point)` returns the exact unary snapshot of facts active at one
  time point
- `restrict_to(interval)` keeps only facts whose support overlaps the interval
  and restricts each remaining fact to canonical overlap support
- `support()`, `to_unary_relation()`, `to_binary_relation()`, and
  `to_nary_relation()` forget time and materialize exact support only
- the relation does not store empty temporal support as a sentinel value, so
  absent facts return `None` rather than an empty support object
- transaction time, bitemporal correction histories, temporal join, and
  temporal composition remain later G5 or later-milestone work

## G6 Relation View Notes

- `RelationView` is the first additive G6 shared-core boundary
- the first landed scope covers only `len`, `is_empty`, and deterministic
  tuple iteration through `tuples()`
- `RelationView` currently applies to `UnaryRelation<T>`,
  `BinaryRelation<A, B>`, and `NaryRelation<T>`
- this first shared view does not normalize tuple shape across arities and
  does not replace concrete APIs such as membership checks, schema access,
  domain/range inspection, support materialization, provenance,
  annotations, or temporal support
- backend abstraction, capability-specific shared traits, common core types,
  and typed-row ergonomics remain later G6 work

## G6 Capability Boundary Notes

- `ExactSupport::exact_support` is the shared relation-level boundary for
  forgetting provenance witnesses, annotation values, or valid-time interval
  support and keeping only exact facts
- `ToExactUnaryRelation`, `ToExactBinaryRelation`, and `ToExactNaryRelation`
  make the released exact support materialization paths explicit for generic
  code without changing the existing concrete methods
- provenance still uses `why(fact)` and `provenance_of(fact)` for witnesses
- annotated relations still use `annotation_of(fact)` for stored annotation
  values
- valid-time relations still use `valid_time_of(fact)` for fact-level
  interval support
- `ValidTimeSupport<T>` is fact-level temporal support for one fact, not the
  exact support of a whole relation
- exact n-ary materialization still requires an explicit schema and still
  reuses current `NaryRelation` validation rules

## G6 Common Core-Type Notes

- G6-C is planning-only: no public `Universe<T>`, `RelError`, or `EvalConfig`
  has landed yet
- the first common core-type candidate is an explicit finite carrier boundary
  because current exact APIs already use carrier-relative semantics
- any later carrier type must stay explicit, deterministic, and semantically
  distinct from support inferred from stored tuples
- module-local errors such as `NaryRelationError` and `IntervalError` remain
  the current public error surface
- a shared `RelError` waits for genuinely shared fallible behavior across
  shipped modules
- a shared `EvalConfig` waits for real optimizer, backend, parallel, timeout,
  or resource-budget choices rather than placeholder configuration knobs

## G7 Carrier Notes

- `FiniteCarrier<T>` is the first executable G7 carrier boundary
- explicit carriers stay semantically distinct from support inferred from
  stored tuples such as `BinaryRelation::carrier()`
- `BinaryRelation::carrier()` still returns an inferred `UnaryRelation<T>`,
  not a `FiniteCarrier<T>`
- deterministic construction, membership checks, `len`, `is_empty`, and
  iteration have landed for explicit finite carriers
- empty carriers stay empty, singleton carriers stay singleton, and
  disconnected values remain visible even when no stored pair mentions them
- `BinaryRelation::identity_on`, `reflexive_transitive_closure_on`,
  `is_reflexive_on`, `is_irreflexive_on`, `is_equivalence_on`, and
  `is_partial_order_on` are the first direct carrier-aware exact operations
- the published `UnaryRelation<T>` carrier arguments still coexist unchanged
  through `identity`, `reflexive_transitive_closure`, `is_reflexive`,
  `is_irreflexive`, `is_equivalence`, and `is_partial_order`
- `to_unary_relation()` remains the explicit bridge into older or generic
  unary-carrier call sites
- ADR 0015 now fixes the broader planning direction: complement and total
  relation still require explicit finite carriers, a public `Universe<T>` is
  still deferred, and the first later executable carrier-domain algebra
  should likely start with binary exact relations

## G7 Example

```rust
use relmath::{BinaryRelation, FiniteCarrier};

let step = BinaryRelation::from_pairs([("Draft", "Review")]);
let states = FiniteCarrier::from_values(["Draft", "Review", "Archived"]);
let inferred = step.carrier();

assert_eq!(inferred.to_vec(), vec!["Draft", "Review"]);
assert_eq!(states.to_vec(), vec!["Archived", "Draft", "Review"]);

let inferred_identity = BinaryRelation::identity(&inferred);
let explicit_identity = BinaryRelation::identity_on(&states);
let reachable = step.reflexive_transitive_closure_on(&states);

assert_eq!(inferred_identity.to_vec(), vec![("Draft", "Draft"), ("Review", "Review")]);
assert!(explicit_identity.contains(&"Archived", &"Archived"));
assert!(reachable.contains(&"Archived", &"Archived"));
assert!(reachable.is_reflexive_on(&states));
assert_eq!(
    reachable.to_vec(),
    step.reflexive_transitive_closure(&states.to_unary_relation())
        .to_vec()
);
```

## G6 Typed-Row And Macro Notes

- G6-D is planning-only: no public `RelRow`, `#[derive(RelRow)]`, `set!`,
  `nrel!`, or related macro surface has landed yet
- the released exact schema-aware n-ary core remains `NaryRelation<T>` with
  explicit schema order and deterministic row order
- future typed-row work should start with explicit adapters or conversion
  traits over the current `NaryRelation<T>` semantics rather than replacing
  the exact core with a second row model
- any later typed-row conversion must be lossless or explicitly fallible and
  must not silently reorder, drop, or invent columns
- the smallest future executable adapter work should stay aligned with the
  current homogeneous cell-type model of `NaryRelation<T>`
- broader heterogeneous typed-record support and proc-macro derives remain
  later work after the conversion boundary is stable
- any later literal macros should be thin constructor sugar over already-
  shipped semantics, and their token grammar and schema expansion rules should
  be treated as versioned public API

## G6 Example

```rust
use relmath::{
    FiniteRelation, RelationView, ToExactBinaryRelation,
    annotated::{AnnotatedRelation, BooleanSemiring},
};

let permissions = AnnotatedRelation::from_facts([
    (("alice", "read"), BooleanSemiring::TRUE),
    (("bob", "approve"), BooleanSemiring::TRUE),
    (("carol", "audit"), BooleanSemiring::FALSE),
]);

let exact = permissions.to_binary_relation();

assert_eq!(exact.len(), 2);
assert_eq!(
    exact.tuples().copied().collect::<Vec<_>>(),
    vec![("alice", "read"), ("bob", "approve")]
);
```

## G5 Example

```rust
use relmath::temporal::{Interval, ValidTimeRelation};

let assignments = ValidTimeRelation::from_facts([
    (("alice", "review"), Interval::new(1, 3).expect("expected valid interval")),
    (("alice", "review"), Interval::new(3, 5).expect("expected valid interval")),
    (("bob", "approve"), Interval::new(2, 4).expect("expected valid interval")),
]);
let audit_window = Interval::new(2, 4).expect("expected valid interval");

assert_eq!(
    assignments
        .valid_time_of(&("alice", "review"))
        .expect("expected support")
        .to_vec(),
    vec![Interval::new(1, 5).expect("expected valid interval")]
);
assert!(assignments.is_active_at(&("alice", "review"), &4));
assert!(!assignments.is_active_at(&("alice", "review"), &5));
assert_eq!(
    assignments.snapshot_at(&3).to_vec(),
    vec![("alice", "review"), ("bob", "approve")]
);
assert_eq!(
    assignments.to_binary_relation().to_vec(),
    vec![("alice", "review"), ("bob", "approve")]
);
assert_eq!(
    assignments
        .restrict_to(&audit_window)
        .valid_time_of(&("alice", "review"))
        .expect("expected support")
        .to_vec(),
    vec![Interval::new(2, 4).expect("expected valid interval")]
);
```

## G4 Example

```rust
use relmath::{
    BinaryRelation,
    annotated::{AnnotatedRelation, Semiring},
};

#[derive(Clone, Debug, PartialEq, Eq)]
struct Count(u8);

impl Semiring for Count {
    fn zero() -> Self {
        Self(0)
    }

    fn one() -> Self {
        Self(1)
    }

    fn add(&self, rhs: &Self) -> Self {
        Self(self.0.saturating_add(rhs.0))
    }

    fn mul(&self, rhs: &Self) -> Self {
        Self(self.0.saturating_mul(rhs.0))
    }
}

let tasks = AnnotatedRelation::from_facts([
    (("Alice", "Review"), Count(1)),
    (("Alice", "Review"), Count(2)),
    (("Bob", "Approve"), Count(1)),
    (("Cara", "Archive"), Count(0)),
]);

assert_eq!(tasks.annotation_of(&("Alice", "Review")), Some(&Count(3)));
assert_eq!(tasks.annotation_of(&("Cara", "Archive")), None);
assert!(!tasks.contains_fact(&("Cara", "Archive")));

let support: BinaryRelation<_, _> = tasks.to_binary_relation();
assert_eq!(
    support.to_vec(),
    vec![("Alice", "Review"), ("Bob", "Approve")]
);
```

## Status

This crate now contains the published G1 unary/binary core plus the released
G2 schema-aware n-ary surface, including stricter schema validation for blank
column names, a std-only named-row interchange boundary, an exact natural join
primitive, and exact keyed grouping with row counts; the released G3
provenance foundation and explanation query surface for exact fact tokens; the
released G4 annotated relation foundation; the released G5 valid-time
relation and temporal operation foundation; and the released G6 shared
relation-view boundary for exact unary, binary, and n-ary relations together
with the explicit exact-support capability boundary for the shipped
provenance, annotated, and valid-time surfaces. The first executable G7 slice
now adds `FiniteCarrier<T>` together with additive carrier-aware exact
operations for identity, reflexive-transitive closure, and carrier-relative
property checks, while complement, total relation, and a broader
`Universe<T>` remain planning-only through ADR 0015.