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
//! Deterministic exact provenance support.
//!
//! In this first G3 slice, a provenance token or label is an explicit stable
//! identifier attached directly to a stored fact. A witness is the exact,
//! deterministic set of provenance tokens attached to one stored fact. A
//! derived tuple is a tuple produced by later inference or algebraic
//! derivation; this module does not compute derived tuples yet. The first
//! explanation query in this module is
//! [`crate::provenance::ProvenanceRelation::why`], which explains only stored
//! base facts. When a fact is absent, `why` returns `None` rather than an
//! empty [`crate::provenance::ProvenanceSet`]. No public rule API lives in
//! this module yet; the first least-fixed-point rule layer is still
//! documented as later G3 work. Relation-level exact support materialization
//! for provenance surfaces is also available generically through
//! [`crate::ExactSupport`].

use std::collections::{BTreeMap, BTreeSet};

use crate::{
    BinaryRelation, NaryRelation, NaryRelationError, UnaryRelation, traits::FiniteRelation,
};

/// Deterministic provenance tokens attached to one stored fact.
///
/// `ProvenanceSet<P>` is the user-visible witness for a fact in this first G3
/// slice. It is an exact `BTreeSet`-backed set of tokens or labels, not a
/// derivation DAG.
///
/// # Examples
///
/// ```rust
/// use relmath::provenance::{ProvenanceRelation, ProvenanceSet};
///
/// let empty = ProvenanceSet::<&str>::default();
/// assert!(empty.is_empty());
///
/// let evidence = ProvenanceRelation::from_facts([
///     (("BRCA1", "BreastCancer"), "paper_12"),
///     (("BRCA1", "BreastCancer"), "curated_panel"),
/// ]);
/// let witness = evidence
///     .why(&("BRCA1", "BreastCancer"))
///     .expect("expected explanation");
///
/// assert_eq!(witness.len(), 2);
/// assert!(witness.contains(&"paper_12"));
/// assert!(witness.contains_token(&"curated_panel"));
/// assert_eq!(
///     witness.iter().copied().collect::<Vec<_>>(),
///     vec!["curated_panel", "paper_12"]
/// );
/// assert_eq!(witness.to_vec(), vec!["curated_panel", "paper_12"]);
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProvenanceSet<P: Ord> {
    tokens: BTreeSet<P>,
}

impl<P: Ord> ProvenanceSet<P> {
    /// Returns the number of provenance tokens in the set.
    #[must_use]
    pub fn len(&self) -> usize {
        self.tokens.len()
    }

    /// Returns `true` when the provenance set contains no tokens.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.tokens.is_empty()
    }

    /// Returns `true` when the set contains the given token.
    #[must_use]
    pub fn contains(&self, token: &P) -> bool {
        self.tokens.contains(token)
    }

    /// Returns `true` when the set contains the given provenance token.
    #[must_use]
    pub fn contains_token(&self, token: &P) -> bool {
        self.contains(token)
    }

    /// Returns an iterator over provenance tokens in deterministic order.
    pub fn iter(&self) -> impl Iterator<Item = &P> {
        self.tokens.iter()
    }

    /// Converts the provenance set into a sorted vector of tokens.
    #[must_use]
    pub fn to_vec(&self) -> Vec<P>
    where
        P: Clone,
    {
        self.tokens.iter().cloned().collect()
    }
}

impl<P: Ord> Default for ProvenanceSet<P> {
    fn default() -> Self {
        Self {
            tokens: BTreeSet::new(),
        }
    }
}

/// Deterministic exact provenance attached to stored facts.
///
/// `ProvenanceRelation<F, P>` maps each stored fact to a deterministic set of
/// provenance tokens. Repeated insertion of the same `(fact, token)` pair is
/// idempotent. Inserting a new token for an existing fact combines provenance
/// by exact set union.
///
/// In this first additive provenance slice, every stored fact is a base fact.
/// The module does not yet derive new tuples. [`Self::why`] explains stored
/// facts only; derived-tuple explanations and `why_not` queries remain later
/// work.
///
/// # Examples
///
/// ```rust
/// use relmath::{UnaryRelation, provenance::ProvenanceRelation};
///
/// let gene_disease = ProvenanceRelation::from_facts([
///     (("BRCA1", "BreastCancer"), "paper_12"),
///     (("BRCA1", "BreastCancer"), "curated_panel"),
///     (("TP53", "BreastCancer"), "paper_77"),
/// ]);
///
/// let supported = gene_disease.to_binary_relation();
/// let brca1 = UnaryRelation::singleton("BRCA1");
///
/// assert_eq!(supported.image(&brca1).to_vec(), vec!["BreastCancer"]);
/// assert_eq!(
///     gene_disease
///         .why(&("BRCA1", "BreastCancer"))
///         .expect("expected explanation")
///         .to_vec(),
///     vec!["curated_panel", "paper_12"]
/// );
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProvenanceRelation<F: Ord, P: Ord> {
    facts: BTreeMap<F, ProvenanceSet<P>>,
}

impl<F: Ord, P: Ord> ProvenanceRelation<F, P> {
    /// Creates an empty provenance relation.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use relmath::provenance::ProvenanceRelation;
    ///
    /// let mut evidence = ProvenanceRelation::new();
    ///
    /// assert!(evidence.insert(("BRCA1", "BreastCancer"), "paper_12"));
    /// assert!(!evidence.insert(("BRCA1", "BreastCancer"), "paper_12"));
    /// assert!(evidence.contains_fact(&("BRCA1", "BreastCancer")));
    /// assert_eq!(
    ///     evidence
    ///         .provenance_of(&("BRCA1", "BreastCancer"))
    ///         .expect("expected explanation")
    ///         .to_vec(),
    ///     vec!["paper_12"]
    /// );
    /// ```
    #[must_use]
    pub fn new() -> Self {
        Self {
            facts: BTreeMap::new(),
        }
    }

    /// Creates a provenance relation from `(fact, token)` entries.
    ///
    /// Repeated facts accumulate tokens by exact set union.
    #[must_use]
    pub fn from_facts<I>(facts: I) -> Self
    where
        I: IntoIterator<Item = (F, P)>,
    {
        facts.into_iter().collect()
    }

    /// Inserts one provenance token for a fact.
    ///
    /// Returns `true` when the token was not already attached to the fact.
    pub fn insert(&mut self, fact: F, token: P) -> bool {
        self.facts.entry(fact).or_default().tokens.insert(token)
    }

    /// Returns `true` when the relation contains the given fact.
    #[must_use]
    pub fn contains_fact(&self, fact: &F) -> bool {
        self.facts.contains_key(fact)
    }

    /// Returns why `fact` is present in this provenance relation.
    ///
    /// When `fact` is present, the returned witness is the deterministic exact
    /// set of provenance tokens attached to that stored fact. When `fact` is
    /// absent, this returns `None`.
    ///
    /// In this first G3 slice, every stored fact is a base fact with at least
    /// one token, so `None` means the relation has no explanation for the fact
    /// because the fact is absent.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use relmath::provenance::ProvenanceRelation;
    ///
    /// let evidence = ProvenanceRelation::from_facts([
    ///     (("BRCA1", "BreastCancer"), "paper_12"),
    ///     (("BRCA1", "BreastCancer"), "curated_panel"),
    /// ]);
    ///
    /// let why = evidence
    ///     .why(&("BRCA1", "BreastCancer"))
    ///     .expect("expected explanation");
    ///
    /// assert!(why.contains_token(&"paper_12"));
    /// assert!(why.contains_token(&"curated_panel"));
    /// assert!(evidence.why(&("BRCA1", "Olaparib")).is_none());
    /// ```
    #[must_use]
    pub fn why(&self, fact: &F) -> Option<&ProvenanceSet<P>> {
        self.facts.get(fact)
    }

    /// Returns the deterministic provenance set for one fact.
    ///
    /// Prefer [`Self::why`] for explanation-oriented queries.
    #[must_use]
    pub fn provenance_of(&self, fact: &F) -> Option<&ProvenanceSet<P>> {
        self.why(fact)
    }

    /// Returns an iterator over facts and provenance in deterministic order.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use relmath::provenance::ProvenanceRelation;
    ///
    /// let evidence = ProvenanceRelation::from_facts([
    ///     (("BRCA1", "BreastCancer"), "paper_12"),
    ///     (("BRCA1", "BreastCancer"), "curated_panel"),
    ///     (("TP53", "BreastCancer"), "paper_77"),
    /// ]);
    ///
    /// assert_eq!(
    ///     evidence
    ///         .iter()
    ///         .map(|(fact, witness)| (*fact, witness.to_vec()))
    ///         .collect::<Vec<_>>(),
    ///     vec![
    ///         (("BRCA1", "BreastCancer"), vec!["curated_panel", "paper_12"]),
    ///         (("TP53", "BreastCancer"), vec!["paper_77"]),
    ///     ]
    /// );
    /// ```
    pub fn iter(&self) -> impl Iterator<Item = (&F, &ProvenanceSet<P>)> {
        self.facts.iter()
    }

    /// Returns the exact support relation of stored facts as a unary relation.
    ///
    /// This support forgets provenance and keeps only which facts are present.
    /// Generic code can access the same relation-level boundary through
    /// [`crate::ExactSupport::exact_support`].
    ///
    /// # Examples
    ///
    /// ```rust
    /// use relmath::provenance::ProvenanceRelation;
    ///
    /// let evidence = ProvenanceRelation::from_facts([
    ///     (("BRCA1", "BreastCancer"), "paper_12"),
    ///     (("BRCA1", "BreastCancer"), "curated_panel"),
    ///     (("TP53", "BreastCancer"), "paper_77"),
    /// ]);
    ///
    /// assert_eq!(
    ///     evidence.support().to_vec(),
    ///     vec![("BRCA1", "BreastCancer"), ("TP53", "BreastCancer")]
    /// );
    /// ```
    #[must_use]
    pub fn support(&self) -> UnaryRelation<F>
    where
        F: Clone,
    {
        self.facts.keys().cloned().collect()
    }
}

impl<F: Ord, P: Ord> Default for ProvenanceRelation<F, P> {
    fn default() -> Self {
        Self::new()
    }
}

impl<F: Ord, P: Ord> FiniteRelation for ProvenanceRelation<F, P> {
    fn len(&self) -> usize {
        self.facts.len()
    }
}

impl<F: Ord, P: Ord> FromIterator<(F, P)> for ProvenanceRelation<F, P> {
    fn from_iter<I: IntoIterator<Item = (F, P)>>(iter: I) -> Self {
        let mut relation = Self::new();
        relation.extend(iter);
        relation
    }
}

impl<F: Ord, P: Ord> Extend<(F, P)> for ProvenanceRelation<F, P> {
    fn extend<I: IntoIterator<Item = (F, P)>>(&mut self, iter: I) {
        for (fact, token) in iter {
            self.insert(fact, token);
        }
    }
}

impl<T: Ord + Clone, P: Ord> ProvenanceRelation<T, P> {
    /// Converts scalar facts into a unary relation support set.
    ///
    /// Generic code can access the same unary materialization boundary through
    /// [`crate::ToExactUnaryRelation`].
    ///
    /// # Examples
    ///
    /// ```rust
    /// use relmath::provenance::ProvenanceRelation;
    ///
    /// let concepts = ProvenanceRelation::from_facts([
    ///     ("Relations", "lecture_1"),
    ///     ("Relations", "worksheet"),
    ///     ("Closure", "lecture_2"),
    /// ]);
    ///
    /// assert_eq!(concepts.to_unary_relation().to_vec(), vec!["Closure", "Relations"]);
    /// ```
    #[must_use]
    pub fn to_unary_relation(&self) -> UnaryRelation<T> {
        self.support()
    }
}

impl<A: Ord + Clone, B: Ord + Clone, P: Ord> ProvenanceRelation<(A, B), P> {
    /// Converts pair facts into a binary relation support set.
    ///
    /// Generic code can access the same binary materialization boundary
    /// through [`crate::ToExactBinaryRelation`].
    ///
    /// # Examples
    ///
    /// ```rust
    /// use relmath::provenance::ProvenanceRelation;
    ///
    /// let evidence = ProvenanceRelation::from_facts([
    ///     (("Alice", "Reader"), "directory"),
    ///     (("Bob", "Editor"), "directory"),
    /// ]);
    ///
    /// assert_eq!(
    ///     evidence.to_binary_relation().to_vec(),
    ///     vec![("Alice", "Reader"), ("Bob", "Editor")]
    /// );
    /// ```
    #[must_use]
    pub fn to_binary_relation(&self) -> BinaryRelation<A, B> {
        self.facts.keys().cloned().collect()
    }
}

impl<T: Ord + Clone, P: Ord> ProvenanceRelation<Vec<T>, P> {
    /// Converts row facts into an n-ary relation with the given schema.
    ///
    /// The explicit schema preserves the current exact n-ary boundary where row
    /// values do not themselves encode column names. This method reuses the
    /// current `NaryRelation` validation rules, so duplicate or blank column
    /// names and row-arity mismatches return `NaryRelationError`. Generic code
    /// can access the same n-ary materialization boundary through
    /// [`crate::ToExactNaryRelation`].
    ///
    /// # Examples
    ///
    /// ```rust
    /// use relmath::provenance::ProvenanceRelation;
    ///
    /// let rows = ProvenanceRelation::from_facts([
    ///     (vec!["Alice", "Math", "passed"], "gradebook"),
    ///     (vec!["Alice", "Math", "passed"], "reviewed"),
    ///     (vec!["Bob", "Physics", "passed"], "gradebook"),
    /// ]);
    ///
    /// let relation = rows
    ///     .to_nary_relation(["student", "course", "status"])
    ///     .expect("expected valid support relation");
    ///
    /// assert_eq!(
    ///     relation.to_rows(),
    ///     vec![
    ///         vec!["Alice", "Math", "passed"],
    ///         vec!["Bob", "Physics", "passed"],
    ///     ]
    /// );
    /// ```
    pub fn to_nary_relation<I, S>(&self, schema: I) -> Result<NaryRelation<T>, NaryRelationError>
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        NaryRelation::from_rows(schema, self.facts.keys().cloned())
    }
}