use std::collections::HashMap;
use std::hash::Hash;
use super::graph;
#[derive(Debug, Clone)]
pub struct CachedTaxonomy<E: Clone + Eq + Hash> {
forward: HashMap<E, Vec<E>>,
reverse: HashMap<E, Vec<E>>,
}
impl<E: Clone + Eq + Hash + std::fmt::Debug> CachedTaxonomy<E> {
pub fn new(relations: &[(E, E)]) -> Self {
Self {
forward: graph::adjacency_map(relations),
reverse: graph::reverse_adjacency_map(relations),
}
}
pub fn ancestors(&self, entity: &E) -> Vec<E> {
graph::reachable(entity, &self.forward)
}
pub fn descendants(&self, entity: &E) -> Vec<E> {
graph::reachable(entity, &self.reverse)
}
pub fn is_a(&self, child: &E, ancestor: &E) -> bool {
if child == ancestor {
return true;
}
self.ancestors(child).contains(ancestor)
}
pub fn has_cycles(&self) -> bool {
self.forward
.keys()
.any(|k| graph::has_cycle(k, &self.forward))
}
pub fn parents(&self, entity: &E) -> &[E] {
self.forward
.get(entity)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn children(&self, entity: &E) -> &[E] {
self.reverse
.get(entity)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
}
#[derive(Debug, Clone)]
pub struct CachedEquivalence<E: Clone + Eq + Hash> {
adj: HashMap<E, Vec<E>>,
}
impl<E: Clone + Eq + Hash + std::fmt::Debug> CachedEquivalence<E> {
pub fn new(pairs: &[(E, E)]) -> Self {
let mut adj: HashMap<E, Vec<E>> = HashMap::new();
for (a, b) in pairs {
if a != b {
adj.entry(a.clone()).or_default().push(b.clone());
adj.entry(b.clone()).or_default().push(a.clone());
}
}
Self { adj }
}
pub fn equivalent_to(&self, entity: &E) -> Vec<E> {
graph::reachable(entity, &self.adj)
.into_iter()
.filter(|e| e != entity)
.collect()
}
pub fn are_equivalent(&self, a: &E, b: &E) -> bool {
if a == b {
return true;
}
self.equivalent_to(a).contains(b)
}
}
#[derive(Debug, Clone)]
pub struct CachedOpposition<E: Clone + Eq + Hash> {
adj: HashMap<E, Vec<E>>,
}
impl<E: Clone + Eq + Hash> CachedOpposition<E> {
pub fn new(pairs: &[(E, E)]) -> Self {
let mut adj: HashMap<E, Vec<E>> = HashMap::new();
for (a, b) in pairs {
adj.entry(a.clone()).or_default().push(b.clone());
adj.entry(b.clone()).or_default().push(a.clone());
}
Self { adj }
}
pub fn opposites(&self, entity: &E) -> &[E] {
self.adj.get(entity).map(|v| v.as_slice()).unwrap_or(&[])
}
pub fn are_opposed(&self, a: &E, b: &E) -> bool {
self.opposites(a).contains(b)
}
}
#[derive(Debug, Clone)]
pub struct CachedMereology<E: Clone + Eq + Hash> {
parts: HashMap<E, Vec<E>>,
wholes: HashMap<E, Vec<E>>,
}
impl<E: Clone + Eq + Hash + std::fmt::Debug> CachedMereology<E> {
pub fn new(relations: &[(E, E)]) -> Self {
Self {
parts: graph::adjacency_map(relations),
wholes: graph::reverse_adjacency_map(relations),
}
}
pub fn parts_of(&self, whole: &E) -> Vec<E> {
graph::reachable(whole, &self.parts)
}
pub fn whole_of(&self, part: &E) -> Vec<E> {
graph::reachable(part, &self.wholes)
}
pub fn direct_parts(&self, whole: &E) -> &[E] {
self.parts.get(whole).map(|v| v.as_slice()).unwrap_or(&[])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Animal {
Dog,
Cat,
Mammal,
Animal,
}
#[test]
fn cached_taxonomy_is_a() {
let t = CachedTaxonomy::new(&[
(Animal::Dog, Animal::Mammal),
(Animal::Cat, Animal::Mammal),
(Animal::Mammal, Animal::Animal),
]);
assert!(t.is_a(&Animal::Dog, &Animal::Animal));
assert!(t.is_a(&Animal::Dog, &Animal::Mammal));
assert!(!t.is_a(&Animal::Dog, &Animal::Cat));
assert!(t.is_a(&Animal::Dog, &Animal::Dog)); }
#[test]
fn cached_taxonomy_parents_children() {
let t =
CachedTaxonomy::new(&[(Animal::Dog, Animal::Mammal), (Animal::Cat, Animal::Mammal)]);
assert_eq!(t.parents(&Animal::Dog), &[Animal::Mammal]);
assert_eq!(t.children(&Animal::Mammal).len(), 2);
}
#[test]
fn cached_taxonomy_no_cycles() {
let t = CachedTaxonomy::new(&[
(Animal::Dog, Animal::Mammal),
(Animal::Mammal, Animal::Animal),
]);
assert!(!t.has_cycles());
}
#[test]
fn cached_equivalence() {
let e = CachedEquivalence::new(&[
(Animal::Dog, Animal::Cat), ]);
assert!(e.are_equivalent(&Animal::Dog, &Animal::Cat));
assert!(e.are_equivalent(&Animal::Cat, &Animal::Dog)); assert!(!e.are_equivalent(&Animal::Dog, &Animal::Mammal));
}
#[test]
fn cached_opposition() {
let o = CachedOpposition::new(&[(Animal::Dog, Animal::Cat)]);
assert!(o.are_opposed(&Animal::Dog, &Animal::Cat));
assert!(o.are_opposed(&Animal::Cat, &Animal::Dog)); assert_eq!(o.opposites(&Animal::Dog), &[Animal::Cat]);
}
#[test]
fn cached_mereology() {
let m = CachedMereology::new(&[
(Animal::Animal, Animal::Mammal),
(Animal::Mammal, Animal::Dog),
]);
assert_eq!(m.direct_parts(&Animal::Animal), &[Animal::Mammal]);
let all_parts = m.parts_of(&Animal::Animal);
assert!(all_parts.contains(&Animal::Mammal));
assert!(all_parts.contains(&Animal::Dog)); }
mod prop {
use super::*;
use proptest::prelude::*;
fn arb_animal() -> impl Strategy<Value = Animal> {
prop_oneof![
Just(Animal::Dog),
Just(Animal::Cat),
Just(Animal::Mammal),
Just(Animal::Animal),
]
}
fn taxonomy() -> CachedTaxonomy<Animal> {
CachedTaxonomy::new(&[
(Animal::Dog, Animal::Mammal),
(Animal::Cat, Animal::Mammal),
(Animal::Mammal, Animal::Animal),
])
}
proptest! {
#[test]
fn prop_taxonomy_reflexive(a in arb_animal()) {
let t = taxonomy();
prop_assert!(t.is_a(&a, &a));
}
#[test]
fn prop_taxonomy_transitive(a in arb_animal(), b in arb_animal(), c in arb_animal()) {
let t = taxonomy();
if t.is_a(&a, &b) && t.is_a(&b, &c) {
prop_assert!(t.is_a(&a, &c));
}
}
#[test]
fn prop_taxonomy_antisymmetric(a in arb_animal(), b in arb_animal()) {
let t = taxonomy();
if t.is_a(&a, &b) && t.is_a(&b, &a) {
prop_assert_eq!(a, b);
}
}
#[test]
fn prop_ancestors_exclude_self(a in arb_animal()) {
let t = taxonomy();
prop_assert!(!t.ancestors(&a).contains(&a));
}
#[test]
fn prop_descendants_exclude_self(a in arb_animal()) {
let t = taxonomy();
prop_assert!(!t.descendants(&a).contains(&a));
}
#[test]
fn prop_parent_child_inverse(a in arb_animal()) {
let t = taxonomy();
for parent in t.parents(&a) {
prop_assert!(t.children(parent).contains(&a));
}
}
#[test]
fn prop_equivalence_reflexive(a in arb_animal()) {
let e = CachedEquivalence::new(&[(Animal::Dog, Animal::Cat)]);
prop_assert!(e.are_equivalent(&a, &a));
}
#[test]
fn prop_equivalence_symmetric(a in arb_animal(), b in arb_animal()) {
let e = CachedEquivalence::new(&[(Animal::Dog, Animal::Cat)]);
if e.are_equivalent(&a, &b) {
prop_assert!(e.are_equivalent(&b, &a));
}
}
#[test]
fn prop_opposition_symmetric(a in arb_animal(), b in arb_animal()) {
let o = CachedOpposition::new(&[(Animal::Dog, Animal::Cat)]);
if o.are_opposed(&a, &b) {
prop_assert!(o.are_opposed(&b, &a));
}
}
#[test]
fn prop_opposition_irreflexive(a in arb_animal()) {
let o = CachedOpposition::new(&[(Animal::Dog, Animal::Cat)]);
prop_assert!(!o.are_opposed(&a, &a));
}
#[test]
fn prop_parts_exclude_self(a in arb_animal()) {
let m = CachedMereology::new(&[
(Animal::Animal, Animal::Mammal),
(Animal::Mammal, Animal::Dog),
]);
prop_assert!(!m.parts_of(&a).contains(&a));
}
#[test]
fn prop_cached_matches_original(a in arb_animal(), b in arb_animal()) {
let relations = vec![
(Animal::Dog, Animal::Mammal),
(Animal::Cat, Animal::Mammal),
(Animal::Mammal, Animal::Animal),
];
let cached = CachedTaxonomy::new(&relations);
let original = crate::ontology::reasoning::taxonomy::is_a::<TestTaxonomy>(&a, &b);
prop_assert_eq!(cached.is_a(&a, &b), original);
}
}
use crate::category::Entity;
use crate::ontology::reasoning::taxonomy::TaxonomyDef;
impl Entity for Animal {
fn variants() -> Vec<Self> {
vec![Animal::Dog, Animal::Cat, Animal::Mammal, Animal::Animal]
}
}
struct TestTaxonomy;
impl TaxonomyDef for TestTaxonomy {
type Entity = Animal;
fn relations() -> Vec<(Animal, Animal)> {
vec![
(Animal::Dog, Animal::Mammal),
(Animal::Cat, Animal::Mammal),
(Animal::Mammal, Animal::Animal),
]
}
}
}
}