relmath-rs 0.7.0

Relation-first mathematics and scientific computing in Rust.
Documentation
//! Integration tests for the G1 unary and binary relation algebra.

use relmath::{BinaryRelation, FiniteRelation, UnaryRelation};

#[test]
fn unary_relations_preserve_set_semantics_in_deterministic_order() {
    let empty = UnaryRelation::<u8>::new();
    assert!(empty.is_empty());

    let xs = UnaryRelation::from_values([3_u8, 1, 2, 2]);
    let ys = UnaryRelation::from_values([2_u8, 3, 4]);

    assert_eq!(xs.to_vec(), vec![1, 2, 3]);
    assert_eq!(xs.union(&ys).to_vec(), vec![1, 2, 3, 4]);
    assert_eq!(xs.intersection(&ys).to_vec(), vec![2, 3]);
    assert_eq!(xs.difference(&ys).to_vec(), vec![1]);
    assert!(UnaryRelation::singleton(2_u8).is_subset(&xs));
}

#[test]
fn binary_relations_cover_core_algebra_and_restrictions() {
    let assignments = BinaryRelation::from_pairs([
        ("ann", "admin"),
        ("ann", "editor"),
        ("bob", "reviewer"),
        ("cara", "guest"),
    ]);
    let extra = BinaryRelation::from_pairs([("ann", "admin"), ("dana", "guest")]);

    assert_eq!(
        assignments.to_vec(),
        vec![
            ("ann", "admin"),
            ("ann", "editor"),
            ("bob", "reviewer"),
            ("cara", "guest"),
        ]
    );
    assert_eq!(assignments.domain().to_vec(), vec!["ann", "bob", "cara"]);
    assert_eq!(
        assignments.range().to_vec(),
        vec!["admin", "editor", "guest", "reviewer"]
    );
    assert_eq!(
        assignments.converse().to_vec(),
        vec![
            ("admin", "ann"),
            ("editor", "ann"),
            ("guest", "cara"),
            ("reviewer", "bob"),
        ]
    );
    assert_eq!(
        assignments.union(&extra).to_vec(),
        vec![
            ("ann", "admin"),
            ("ann", "editor"),
            ("bob", "reviewer"),
            ("cara", "guest"),
            ("dana", "guest"),
        ]
    );
    assert_eq!(
        assignments.intersection(&extra).to_vec(),
        vec![("ann", "admin")]
    );
    assert_eq!(
        assignments.difference(&extra).to_vec(),
        vec![("ann", "editor"), ("bob", "reviewer"), ("cara", "guest")]
    );

    let allowed_users = UnaryRelation::from_values(["ann", "bob"]);
    let allowed_roles = UnaryRelation::from_values(["admin", "reviewer"]);

    assert_eq!(
        assignments.restrict_domain(&allowed_users).to_vec(),
        vec![("ann", "admin"), ("ann", "editor"), ("bob", "reviewer")]
    );
    assert_eq!(
        assignments.restrict_range(&allowed_roles).to_vec(),
        vec![("ann", "admin"), ("bob", "reviewer")]
    );
    assert_eq!(
        assignments
            .image(&UnaryRelation::from_values(["ann", "cara"]))
            .to_vec(),
        vec!["admin", "editor", "guest"]
    );
    assert_eq!(
        assignments
            .preimage(&UnaryRelation::from_values(["guest", "reviewer"]))
            .to_vec(),
        vec!["bob", "cara"]
    );
}

#[test]
fn identity_and_basic_laws_hold_for_homogeneous_relations() {
    let parent = BinaryRelation::from_pairs([("Ada", "Bob"), ("Bob", "Cara")]);
    let people = UnaryRelation::from_values(["Ada", "Bob", "Cara"]);
    let identity = BinaryRelation::identity(&people);

    assert_eq!(parent.compose(&identity).to_vec(), parent.to_vec());
    assert_eq!(identity.compose(&parent).to_vec(), parent.to_vec());
    assert_eq!(parent.converse().converse().to_vec(), parent.to_vec());

    let step = BinaryRelation::from_pairs([
        ("Draft", "Review"),
        ("Review", "Approved"),
        ("Review", "Rejected"),
    ]);
    let states = UnaryRelation::from_values(["Approved", "Draft", "Rejected", "Review"]);

    assert!(identity.is_reflexive(&people));
    assert!(step.is_irreflexive(&states));
    assert!(!step.is_symmetric());
    assert!(step.is_antisymmetric());
    assert!(!step.is_transitive());

    let closure = step.transitive_closure();
    let reachable = step.reflexive_transitive_closure(&states);

    assert!(closure.is_transitive());
    assert!(reachable.contains(&"Draft", &"Draft"));
    assert!(reachable.contains(&"Draft", &"Approved"));
    assert!(reachable.contains(&"Draft", &"Rejected"));
}

#[test]
fn empty_relations_cover_core_edge_cases() {
    let empty_unary = UnaryRelation::<u8>::new();
    let empty_binary = BinaryRelation::<u8, u8>::new();
    let carrier = UnaryRelation::from_values([1_u8, 2_u8]);

    assert!(empty_unary.is_empty());
    assert!(empty_binary.is_empty());
    assert!(empty_binary.domain().is_empty());
    assert!(empty_binary.range().is_empty());
    assert!(empty_binary.converse().is_empty());
    assert!(
        empty_binary
            .compose(&BinaryRelation::from_pairs([(1_u8, 2_u8)]))
            .is_empty()
    );
    assert!(BinaryRelation::<u8, u8>::identity(&empty_unary).is_empty());
    assert!(empty_binary.transitive_closure().is_empty());

    let reflexive = empty_binary.reflexive_transitive_closure(&carrier);
    assert_eq!(reflexive.to_vec(), vec![(1_u8, 1_u8), (2_u8, 2_u8)]);
}

#[test]
fn singleton_relations_cover_boundary_properties() {
    let carrier = UnaryRelation::singleton("Ada");
    let self_loop = BinaryRelation::from_pairs([("Ada", "Ada")]);

    assert_eq!(carrier.to_vec(), vec!["Ada"]);
    assert_eq!(self_loop.domain().to_vec(), vec!["Ada"]);
    assert_eq!(self_loop.range().to_vec(), vec!["Ada"]);
    assert_eq!(
        self_loop.transitive_closure().to_vec(),
        vec![("Ada", "Ada")]
    );
    assert_eq!(
        self_loop.reflexive_transitive_closure(&carrier).to_vec(),
        vec![("Ada", "Ada")]
    );
    assert!(self_loop.is_equivalence(&carrier));
    assert!(self_loop.is_partial_order(&carrier));
}

#[test]
fn composition_runs_left_then_right() {
    let user_role = BinaryRelation::from_pairs([("ann", "admin"), ("bob", "reviewer")]);
    let role_permission = BinaryRelation::from_pairs([("admin", "read"), ("reviewer", "approve")]);

    assert_eq!(
        user_role.compose(&role_permission).to_vec(),
        vec![("ann", "read"), ("bob", "approve")]
    );
}

#[test]
fn reflexive_transitive_closure_adds_disconnected_carrier_values() {
    let step = BinaryRelation::from_pairs([("Draft", "Review")]);
    let states = UnaryRelation::from_values(["Archived", "Draft", "Review"]);

    let transitive = step.transitive_closure();
    let reflexive = step.reflexive_transitive_closure(&states);

    assert!(!transitive.contains(&"Archived", &"Archived"));
    assert!(reflexive.contains(&"Archived", &"Archived"));
    assert!(reflexive.contains(&"Draft", &"Draft"));
    assert!(reflexive.contains(&"Draft", &"Review"));
}