diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c139ee3..1e0ba8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: toolchain: ${{ matrix.toolchain }} - name: Run tests - run: cargo test --release + run: cargo test run-all-features: runs-on: ubuntu-latest @@ -37,7 +37,7 @@ jobs: toolchain: "stable" - name: Run tests - run: cargo test --features serde,pyo3 --release + run: cargo test --features serde,pyo3 format: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index a2e6921..03bbf06 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ Cargo.lock # IDE files .vscode/ .idea/ +.zed/ # Perf data perf.data* diff --git a/Cargo.toml b/Cargo.toml index fb0a1c2..b68f6b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ontolius" -version = "0.7.3" +version = "0.7.4" description = "A fast and safe crate for working with biomedical ontologies." keywords = ["ontology", "bioinformatics", "HPO", "MAxO", "GO"] edition = "2021" diff --git a/src/ontology/api.rs b/src/ontology/api.rs index 7ad68f1..7668830 100644 --- a/src/ontology/api.rs +++ b/src/ontology/api.rs @@ -125,6 +125,8 @@ where } /// Traversals in the ontology index space. +/// +/// The child-parent relationship is established solely via the `is_a` relationship. pub trait HierarchyTraversals { /// Get the index of the `query` term or `None` if the term is unknown. fn term_index(&self, query: &Q) -> Option @@ -208,6 +210,8 @@ pub trait HierarchyWalks { } /// Tests if an ontology term is a parent, a child, an ancestor, or descendant of another term. +/// +/// The child-parent relationship is established solely via the `is_a` relationship. pub trait HierarchyQueries { /// Test if `sub` is child of `obj`. /// diff --git a/src/ontology/csr/beta.rs b/src/ontology/csr/beta.rs index 6e26b3b..db51241 100644 --- a/src/ontology/csr/beta.rs +++ b/src/ontology/csr/beta.rs @@ -115,6 +115,31 @@ where } } +macro_rules! impl_ontology_terms { + ($t:ty) => { + impl OntologyTerms for $t + where + I: Idx, + { + fn iter_terms<'a>(&'a self) -> impl Iterator + where + T: 'a, + { + (**self).iter_terms() + } + + fn term_by_id(&self, id: &ID) -> Option<&T> + where + ID: Identified, + { + (**self).term_by_id(id) + } + } + }; +} +impl_ontology_terms!(&CsrOntology); +impl_ontology_terms!(Box>); + impl HierarchyTraversals for CsrOntology where I: Idx + Hash, @@ -150,6 +175,39 @@ where } } } +macro_rules! impl_hierarchy_traversal { + ($t:ty) => { + impl HierarchyTraversals for $t + where + I: Idx + Hash, + { + fn term_index(&self, query: &Q) -> Option + where + Q: Identified, + { + (**self).term_index(query) + } + + fn iter_child_idxs(&self, query: I) -> impl Iterator { + (**self).iter_child_idxs(query) + } + + fn iter_descendant_idxs(&self, query: I) -> impl Iterator { + (**self).iter_descendant_idxs(query) + } + + fn iter_parent_idxs(&self, query: I) -> impl Iterator { + (**self).iter_descendant_idxs(query) + } + + fn iter_ancestor_idxs(&self, query: I) -> impl Iterator { + (**self).iter_ancestor_idxs(query) + } + } + }; +} +impl_hierarchy_traversal!(&CsrOntology); +impl_hierarchy_traversal!(Box>); impl HierarchyWalks for CsrOntology where @@ -213,6 +271,46 @@ where } } +macro_rules! impl_hierarchy_walks { + ($t:ty) => { + impl HierarchyWalks for $t + where + I: Idx + Hash, + T: Identified, + { + fn iter_parent_ids<'a, ID>(&'a self, query: &ID) -> impl Iterator + where + ID: Identified, + { + (**self).iter_parent_ids(query) + } + + fn iter_child_ids<'a, ID>(&'a self, query: &ID) -> impl Iterator + where + ID: Identified, + { + (**self).iter_child_ids(query) + } + + fn iter_ancestor_ids<'a, ID>(&'a self, query: &ID) -> impl Iterator + where + ID: Identified, + { + (**self).iter_ancestor_ids(query) + } + + fn iter_descendant_ids<'a, ID>(&'a self, query: &ID) -> impl Iterator + where + ID: Identified, + { + (**self).iter_descendant_ids(query) + } + } + }; +} +impl_hierarchy_walks!(&CsrOntology); +impl_hierarchy_walks!(Box>); + impl HierarchyQueries for CsrOntology where I: Idx + Hash, @@ -274,6 +372,49 @@ where } } +macro_rules! impl_hierarchy_queries { + ($t:ty) => { + impl HierarchyQueries for $t + where + I: Idx + Hash, + { + fn is_child_of(&self, sub: &S, obj: &O) -> bool + where + S: Identified, + O: Identified, + { + (**self).is_child_of(sub, obj) + } + + fn is_descendant_of(&self, sub: &S, obj: &O) -> bool + where + S: Identified, + O: Identified, + { + (**self).is_descendant_of(sub, obj) + } + + fn is_parent_of(&self, sub: &S, obj: &O) -> bool + where + S: Identified, + O: Identified, + { + (**self).is_parent_of(sub, obj) + } + + fn is_ancestor_of(&self, sub: &S, obj: &O) -> bool + where + S: Identified, + O: Identified, + { + (**self).is_ancestor_of(sub, obj) + } + } + }; +} +impl_hierarchy_queries!(&CsrOntology); +impl_hierarchy_queries!(Box>); + impl MetadataAware for CsrOntology where I: Idx, diff --git a/src/term.rs b/src/term.rs index a7b3ffa..4bb41db 100644 --- a/src/term.rs +++ b/src/term.rs @@ -135,6 +135,12 @@ pub mod simple { } } + impl Identified for &SimpleMinimalTerm { + fn identifier(&self) -> &TermId { + (**self).identifier() + } + } + impl AltTermIdAware for SimpleMinimalTerm { type TermIdIter<'a> = std::slice::Iter<'a, TermId> @@ -150,6 +156,21 @@ pub mod simple { } } + impl AltTermIdAware for &SimpleMinimalTerm { + type TermIdIter<'a> + = std::slice::Iter<'a, TermId> + where + Self: 'a; + + fn iter_alt_term_ids(&self) -> Self::TermIdIter<'_> { + (**self).iter_alt_term_ids() + } + + fn alt_term_id_count(&self) -> usize { + (**self).alt_term_id_count() + } + } + impl MinimalTerm for SimpleMinimalTerm { fn name(&self) -> &str { self.name.as_str() @@ -160,6 +181,16 @@ pub mod simple { } } + impl MinimalTerm for &SimpleMinimalTerm { + fn name(&self) -> &str { + (**self).name() + } + + fn is_current(&self) -> bool { + (**self).is_current() + } + } + #[derive(Debug, PartialEq, Eq, Clone)] pub struct SimpleTerm { term_id: TermId, diff --git a/tests/test_ontology.rs b/tests/test_ontology.rs new file mode 100644 index 0000000..6e20d1d --- /dev/null +++ b/tests/test_ontology.rs @@ -0,0 +1,28 @@ +mod csr { + + use ontolius::ontology::{ + csr::MinimalCsrOntology, HierarchyQueries, HierarchyTraversals, HierarchyWalks, + OntologyTerms, + }; + + #[test] + fn test_csr_ontology_can_be_used_with_trait_bounds() { + let o: Option = None; + + if o.is_some() { + // This will never run. It does not matter, + // since it is enough that the code compiles. + let o: &MinimalCsrOntology = o.as_ref().unwrap(); + + pretend_to_use_hierarchy_queries(o); + pretend_to_use_hierarchy_walks(o); + pretend_to_use_hierarchy_traversals(o); + pretend_to_use_ontology_terms(o); + } + + fn pretend_to_use_hierarchy_queries(_o: impl HierarchyQueries) {} + fn pretend_to_use_hierarchy_walks(_o: impl HierarchyWalks) {} + fn pretend_to_use_hierarchy_traversals(_o: impl HierarchyTraversals) {} + fn pretend_to_use_ontology_terms(_o: impl OntologyTerms) {} + } +}