relmath-rs 0.6.0

Relation-first mathematics and scientific computing in Rust.
Documentation
//! Shared capability traits for exact-support materialization.
//!
//! These traits make the released add-on relation surfaces line up around one
//! explicit question: what exact facts remain after forgetting provenance
//! witnesses, annotation values, or valid-time intervals?

use crate::{
    BinaryRelation, NaryRelation, NaryRelationError, UnaryRelation,
    annotated::{AnnotatedRelation, Semiring},
    provenance::ProvenanceRelation,
    temporal::ValidTimeRelation,
};

use super::FiniteRelation;

/// Relation-level exact support for add-on relation surfaces.
///
/// `ExactSupport` makes explicit the deterministic relation-level boundary
/// already used by the released provenance, annotated, and valid-time
/// surfaces: forget the add-on payload and keep only the exact stored facts.
///
/// The forgotten payload differs by surface:
///
/// - provenance forgets the witness token set returned by
///   [`crate::provenance::ProvenanceRelation::why`];
/// - annotated relations forget the stored annotation returned by
///   [`crate::annotated::AnnotatedRelation::annotation_of`];
/// - valid-time relations forget the fact-level interval support returned by
///   [`crate::temporal::ValidTimeRelation::valid_time_of`].
///
/// Do not confuse this relation-level exact support with
/// [`crate::temporal::ValidTimeSupport`], which is the canonical interval
/// support for one stored fact rather than the exact support of the whole
/// relation.
///
/// # Examples
///
/// ```rust
/// use relmath::{
///     ExactSupport,
///     annotated::{AnnotatedRelation, BooleanSemiring},
///     provenance::ProvenanceRelation,
///     temporal::{Interval, ValidTimeRelation},
/// };
///
/// let evidence = ProvenanceRelation::from_facts([
///     (("alice", "review"), "directory"),
///     (("bob", "approve"), "policy"),
/// ]);
/// let permissions = AnnotatedRelation::from_facts([
///     (("alice", "review"), BooleanSemiring::TRUE),
///     (("bob", "approve"), BooleanSemiring::TRUE),
/// ]);
/// let schedule = ValidTimeRelation::from_facts([
///     (
///         ("alice", "review"),
///         Interval::new(1, 3).expect("expected valid interval"),
///     ),
///     (
///         ("bob", "approve"),
///         Interval::new(2, 4).expect("expected valid interval"),
///     ),
/// ]);
///
/// assert_eq!(
///     evidence.exact_support().to_vec(),
///     vec![("alice", "review"), ("bob", "approve")]
/// );
/// assert_eq!(permissions.exact_support().to_vec(), evidence.exact_support().to_vec());
/// assert_eq!(schedule.exact_support().to_vec(), evidence.exact_support().to_vec());
/// ```
pub trait ExactSupport<F>: FiniteRelation
where
    F: Ord + Clone,
{
    /// Returns the exact support of stored facts in deterministic fact order.
    #[must_use]
    fn exact_support(&self) -> UnaryRelation<F>;
}

impl<F: Ord + Clone, P: Ord> ExactSupport<F> for ProvenanceRelation<F, P> {
    fn exact_support(&self) -> UnaryRelation<F> {
        self.support()
    }
}

impl<F: Ord + Clone, A: Semiring> ExactSupport<F> for AnnotatedRelation<F, A> {
    fn exact_support(&self) -> UnaryRelation<F> {
        self.support()
    }
}

impl<F: Ord + Clone, T: Ord + Clone> ExactSupport<F> for ValidTimeRelation<F, T> {
    fn exact_support(&self) -> UnaryRelation<F> {
        self.support()
    }
}

/// Materializes exact scalar support as a unary relation.
///
/// This trait mirrors the existing `to_unary_relation` conversion methods on
/// released add-on surfaces and gives generic code one explicit unary
/// materialization boundary.
///
/// # Examples
///
/// ```rust
/// use relmath::{
///     ToExactUnaryRelation,
///     annotated::{AnnotatedRelation, BooleanSemiring},
/// };
///
/// fn exact_values<R>(relation: &R) -> Vec<&'static str>
/// where
///     R: ToExactUnaryRelation<&'static str>,
/// {
///     relation.to_unary_relation().to_vec()
/// }
///
/// let concepts = AnnotatedRelation::from_facts([
///     ("Closure", BooleanSemiring::TRUE),
///     ("Relations", BooleanSemiring::TRUE),
///     ("Zero", BooleanSemiring::FALSE),
/// ]);
///
/// assert_eq!(exact_values(&concepts), vec!["Closure", "Relations"]);
/// ```
pub trait ToExactUnaryRelation<T>: ExactSupport<T>
where
    T: Ord + Clone,
{
    /// Materializes exact scalar support as a unary relation.
    #[must_use]
    fn to_unary_relation(&self) -> UnaryRelation<T> {
        self.exact_support()
    }
}

impl<T, R> ToExactUnaryRelation<T> for R
where
    T: Ord + Clone,
    R: ExactSupport<T>,
{
}

/// Materializes exact pair support as a binary relation.
///
/// This trait mirrors the existing `to_binary_relation` conversion methods on
/// released add-on surfaces and keeps deterministic pair order inherited from
/// exact support materialization.
///
/// # Examples
///
/// ```rust
/// use relmath::{
///     BinaryRelation, ToExactBinaryRelation,
///     annotated::{AnnotatedRelation, BooleanSemiring},
/// };
///
/// fn exact_pairs<R>(relation: &R) -> BinaryRelation<&'static str, &'static str>
/// where
///     R: ToExactBinaryRelation<&'static str, &'static str>,
/// {
///     relation.to_binary_relation()
/// }
///
/// let permissions = AnnotatedRelation::from_facts([
///     (("alice", "read"), BooleanSemiring::TRUE),
///     (("bob", "approve"), BooleanSemiring::TRUE),
/// ]);
///
/// assert_eq!(
///     exact_pairs(&permissions).to_vec(),
///     vec![("alice", "read"), ("bob", "approve")]
/// );
/// ```
pub trait ToExactBinaryRelation<A, B>: ExactSupport<(A, B)>
where
    A: Ord + Clone,
    B: Ord + Clone,
{
    /// Materializes exact pair support as a binary relation.
    #[must_use]
    fn to_binary_relation(&self) -> BinaryRelation<A, B> {
        self.exact_support().into_iter().collect()
    }
}

impl<A, B, R> ToExactBinaryRelation<A, B> for R
where
    A: Ord + Clone,
    B: Ord + Clone,
    R: ExactSupport<(A, B)>,
{
}

/// Materializes exact row support as an n-ary relation with an explicit schema.
///
/// This trait mirrors the existing `to_nary_relation` conversion methods on
/// released add-on surfaces. It preserves the current exact n-ary contract:
/// schema names remain explicit and `NaryRelation` validation rules still
/// apply during materialization.
///
/// # Examples
///
/// ```rust
/// use relmath::{NaryRelation, ToExactNaryRelation, provenance::ProvenanceRelation};
///
/// let rows = ProvenanceRelation::from_facts([
///     (vec!["Alice", "Math", "passed"], "gradebook"),
///     (vec!["Bob", "Physics", "passed"], "gradebook"),
/// ]);
///
/// assert_eq!(
///     rows.to_nary_relation(["student", "course", "status"])?,
///     NaryRelation::from_rows(
///         ["student", "course", "status"],
///         [["Alice", "Math", "passed"], ["Bob", "Physics", "passed"]],
///     )?
/// );
///
/// # Ok::<(), relmath::NaryRelationError>(())
/// ```
pub trait ToExactNaryRelation<T>: ExactSupport<Vec<T>>
where
    T: Ord + Clone,
{
    /// Materializes exact row support as an n-ary relation with the given
    /// schema.
    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.exact_support())
    }
}

impl<T, R> ToExactNaryRelation<T> for R
where
    T: Ord + Clone,
    R: ExactSupport<Vec<T>>,
{
}