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"));
}