diff --git a/Cargo.toml b/Cargo.toml index 375641d..3d2fba8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,9 +36,5 @@ harness = false name = "ancestors" harness = false -[[bench]] -name = "parser" -harness = false - [profile.release] lto = true diff --git a/src/annotations/gene.rs b/src/annotations/gene.rs index 1f8eb7c..d92adcf 100644 --- a/src/annotations/gene.rs +++ b/src/annotations/gene.rs @@ -119,11 +119,6 @@ impl Gene { &self.name } - /// Connect another [HPO term](`crate::HpoTerm`) to the gene - pub fn add_term>(&mut self, term_id: I) -> bool { - self.hpos.insert(term_id) - } - /// The set of connected HPO terms pub fn hpo_terms(&self) -> &HpoGroup { &self.hpos @@ -193,6 +188,16 @@ impl Gene { pub fn to_hpo_set<'a>(&self, ontology: &'a Ontology) -> HpoSet<'a> { HpoSet::new(ontology, self.hpos.clone()) } + + /// Connect another [HPO term](`crate::HpoTerm`) to the gene + /// + /// # Note + /// + /// This method does **not** add the [`Gene`] to the [HPO term](`crate::HpoTerm`). + /// Clients should not use this method, unless they are creating their own Ontology. + pub fn add_term>(&mut self, term_id: I) -> bool { + self.hpos.insert(term_id) + } } impl PartialEq for Gene { diff --git a/src/annotations/omim_disease.rs b/src/annotations/omim_disease.rs index 892ee1e..f08aa02 100644 --- a/src/annotations/omim_disease.rs +++ b/src/annotations/omim_disease.rs @@ -95,11 +95,6 @@ impl OmimDisease { &self.name } - /// Connect another [HPO term](`crate::HpoTerm`) to the disease - pub fn add_term>(&mut self, term_id: I) -> bool { - self.hpos.insert(term_id) - } - /// The set of connected HPO terms pub fn hpo_terms(&self) -> &HpoGroup { &self.hpos @@ -167,6 +162,16 @@ impl OmimDisease { pub fn to_hpo_set<'a>(&self, ontology: &'a Ontology) -> HpoSet<'a> { HpoSet::new(ontology, self.hpos.clone()) } + + /// Connect another [HPO term](`crate::HpoTerm`) to the disease + /// + /// # Note + /// + /// This method does **not** add the [`OmimDisease`] to the [HPO term](`crate::HpoTerm`). + /// Clients should not use this method, unless they are creating their own Ontology. + pub fn add_term>(&mut self, term_id: I) -> bool { + self.hpos.insert(term_id) + } } impl PartialEq for OmimDisease { diff --git a/src/lib.rs b/src/lib.rs index b28dd60..1ef6d1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod utils; pub use ontology::comparison; pub use ontology::Ontology; pub use set::HpoSet; +#[doc(inline)] pub use term::{HpoTerm, HpoTermId}; const DEFAULT_NUM_PARENTS: usize = 10; diff --git a/src/ontology.rs b/src/ontology.rs index 5a3f66d..26f8ef0 100644 --- a/src/ontology.rs +++ b/src/ontology.rs @@ -622,79 +622,6 @@ impl Ontology { } } - /// Returns a binary representation of the Ontology - /// - /// The binary data is separated into sections: - /// - /// - Metadata (HPO and Bindat Version) (see `Ontology::metadata_as_bytes`) - /// - Terms (Names + IDs) (see `HpoTermInternal::as_bytes`) - /// - Term - Parent connection (Child ID - Parent ID) - /// (see `HpoTermInternal::parents_as_byte`) - /// - Genes (Names + IDs + Connected HPO Terms) ([`Gene::as_bytes`]) - /// - OMIM Diseases (Names + IDs + Connected HPO Terms) - /// ([`OmimDisease::as_bytes`]) - /// - /// Every section starts with 4 bytes to indicate its size - /// (big-endian encoded `u32`) - /// - /// This method is only useful if you use are modifying the ontology - /// and want to save data for later re-use. - /// - /// # Panics - /// - /// Panics when the buffer length of any subsegment larger than `u32::MAX` - /// - /// # Examples - /// - /// ``` - /// use hpo::Ontology; - /// let ontology = Ontology::from_binary("tests/example.hpo").unwrap(); - /// let bytes = ontology.as_bytes(); - /// ``` - pub fn as_bytes(&self) -> Vec { - fn usize_to_u32(n: usize) -> u32 { - n.try_into().expect("unable to convert {n} to u32") - } - let mut res = Vec::new(); - - // Add metadata, version info - res.append(&mut self.metadata_as_bytes()); - - // All HPO Terms - let mut buffer = Vec::new(); - for term in self.hpo_terms.values() { - buffer.append(&mut term.as_bytes()); - } - res.append(&mut usize_to_u32(buffer.len()).to_be_bytes().to_vec()); - res.append(&mut buffer); - - // All Term - Parent connections - buffer.clear(); - for term in self.hpo_terms.values() { - buffer.append(&mut term.parents_as_byte()); - } - res.append(&mut usize_to_u32(buffer.len()).to_be_bytes().to_vec()); - res.append(&mut buffer); - - // Genes and Gene-Term connections - buffer.clear(); - for gene in self.genes.values() { - buffer.append(&mut gene.as_bytes()); - } - res.append(&mut usize_to_u32(buffer.len()).to_be_bytes().to_vec()); - res.append(&mut buffer); - - // OMIM Disease and Disease-Term connections - buffer.clear(); - for omim_disease in self.omim_diseases.values() { - buffer.append(&mut omim_disease.as_bytes()); - } - res.append(&mut usize_to_u32(buffer.len()).to_be_bytes().to_vec()); - res.append(&mut buffer); - - res - } - /// Returns the number of HPO-Terms in the Ontology /// /// # Examples @@ -721,6 +648,16 @@ impl Ontology { self.len() == 0 } + /// Returns the Jax-Ontology release version + /// + /// e.g. `2023-03-13` + pub fn hpo_version(&self) -> String { + format!( + "{:0>4}-{:0>2}-{:0>2}", + self.hpo_version.0, self.hpo_version.1, self.hpo_version.2, + ) + } + /// Returns the [`HpoTerm`] of the provided [`HpoTermId`] /// /// If no such term is present in the Ontolgy, `None` is returned @@ -738,22 +675,6 @@ impl Ontology { HpoTerm::try_new(self, term_id).ok() } - /// Returns an Iterator of all [`HpoTerm`]s from the Ontology - /// - /// # Examples - /// - /// ``` - /// use hpo::Ontology; - /// let ontology = Ontology::from_binary("tests/example.hpo").unwrap(); - /// for term in ontology.hpos() { - /// println!("{}", term.name()); - /// } - /// ``` - /// - pub fn hpos(&self) -> Iter<'_> { - self.into_iter() - } - /// Returns a reference to the [`Gene`] of the provided [`GeneId`] /// /// If no such gene is present, `None` is returned @@ -882,16 +803,6 @@ impl Ontology { .find(|&disease| disease.name().contains(substring)) } - /// Returns the Jax-Ontology release version - /// - /// e.g. `2023-03-13` - pub fn hpo_version(&self) -> String { - format!( - "{:0>4}-{:0>2}-{:0>2}", - self.hpo_version.0, self.hpo_version.1, self.hpo_version.2, - ) - } - /// Compares `self` to another `Ontology` to identify added/removed terms, genes and diseases /// /// # Examples @@ -914,6 +825,146 @@ impl Ontology { Comparison::new(self, other) } + /// Iterates [`HpoTerm`]s + /// + /// # Examples + /// + /// ```rust + /// use hpo::Ontology; + /// let ontology = Ontology::from_binary("tests/example.hpo").unwrap(); + /// let mut ont_iter = ontology.iter(); + /// + /// assert!(ont_iter.next().is_some()); + /// ``` + /// + pub fn iter(&self) -> Iter<'_> { + Iter { + inner: self.hpo_terms.iter(), + ontology: self, + } + } + + /// Returns an Iterator of all [`HpoTerm`]s from the Ontology + /// + /// # Examples + /// + /// ``` + /// use hpo::Ontology; + /// let ontology = Ontology::from_binary("tests/example.hpo").unwrap(); + /// for term in ontology.hpos() { + /// println!("{}", term.name()); + /// } + /// ``` + /// + /// # Note + /// + /// This method is deprecated and will be removed in the future. + /// Use either [`Ontology::iter`] or iterate the ontology directly: + /// + /// ```rust + /// use hpo::Ontology; + /// let ontology = Ontology::from_binary("tests/example.hpo").unwrap(); + /// for term in &ontology { + /// println!("{}", term.name()); + /// } + /// ``` + /// + pub fn hpos(&self) -> Iter<'_> { + self.into_iter() + } + + /// Returns a reference to the categories of the Ontology + /// + /// Categories are top-level `HpoTermId`s used for + /// categorizing individual `HpoTerm`s. + /// + /// See [`Ontology::set_default_categories()`] for more information + /// + /// # Examples + /// + /// ``` + /// use hpo::{HpoTerm, Ontology}; + /// + /// let mut ontology = Ontology::from_binary("tests/example.hpo").unwrap(); + /// assert_eq!(ontology.categories().len(), 6); + /// ``` + pub fn categories(&self) -> &HpoGroup { + &self.categories + } + + /// Returns a binary representation of the Ontology + /// + /// The binary data is separated into sections: + /// + /// - Metadata (HPO and Bindat Version) (see `Ontology::metadata_as_bytes`) + /// - Terms (Names + IDs) (see `HpoTermInternal::as_bytes`) + /// - Term - Parent connection (Child ID - Parent ID) + /// (see `HpoTermInternal::parents_as_byte`) + /// - Genes (Names + IDs + Connected HPO Terms) ([`Gene::as_bytes`]) + /// - OMIM Diseases (Names + IDs + Connected HPO Terms) + /// ([`OmimDisease::as_bytes`]) + /// + /// Every section starts with 4 bytes to indicate its size + /// (big-endian encoded `u32`) + /// + /// This method is only useful if you use are modifying the ontology + /// and want to save data for later re-use. + /// + /// # Panics + /// + /// Panics when the buffer length of any subsegment larger than `u32::MAX` + /// + /// # Examples + /// + /// ``` + /// use hpo::Ontology; + /// let ontology = Ontology::from_binary("tests/example.hpo").unwrap(); + /// let bytes = ontology.as_bytes(); + /// ``` + pub fn as_bytes(&self) -> Vec { + fn usize_to_u32(n: usize) -> u32 { + n.try_into().expect("unable to convert {n} to u32") + } + let mut res = Vec::new(); + + // Add metadata, version info + res.append(&mut self.metadata_as_bytes()); + + // All HPO Terms + let mut buffer = Vec::new(); + for term in self.hpo_terms.values() { + buffer.append(&mut term.as_bytes()); + } + res.append(&mut usize_to_u32(buffer.len()).to_be_bytes().to_vec()); + res.append(&mut buffer); + + // All Term - Parent connections + buffer.clear(); + for term in self.hpo_terms.values() { + buffer.append(&mut term.parents_as_byte()); + } + res.append(&mut usize_to_u32(buffer.len()).to_be_bytes().to_vec()); + res.append(&mut buffer); + + // Genes and Gene-Term connections + buffer.clear(); + for gene in self.genes.values() { + buffer.append(&mut gene.as_bytes()); + } + res.append(&mut usize_to_u32(buffer.len()).to_be_bytes().to_vec()); + res.append(&mut buffer); + + // OMIM Disease and Disease-Term connections + buffer.clear(); + for omim_disease in self.omim_diseases.values() { + buffer.append(&mut omim_disease.as_bytes()); + } + res.append(&mut usize_to_u32(buffer.len()).to_be_bytes().to_vec()); + res.append(&mut buffer); + + res + } + /// Constructs a smaller ontology that contains only the `leaves` terms and /// all terms needed to connect to each leaf to `root` /// @@ -1055,26 +1106,13 @@ impl Ontology { code.push_str("}\n"); code } +} - /// Returns a reference to the categories of the Ontology - /// - /// Categories are top-level `HpoTermId`s used for - /// categorizing individual `HpoTerm`s. - /// - /// See [`Ontology::set_default_categories()`] for more information - /// - /// # Examples - /// - /// ``` - /// use hpo::{HpoTerm, Ontology}; - /// - /// let mut ontology = Ontology::from_binary("tests/example.hpo").unwrap(); - /// assert_eq!(ontology.categories().len(), 6); - /// ``` - pub fn categories(&self) -> &HpoGroup { - &self.categories - } - +/// Methods to add annotations +/// +/// These methods should rarely (if ever) be used by clients. +/// Calling these functions might disrupt the Ontology and associated terms. +impl Ontology { /// Returns a mutable reference to the categories vector /// /// This is a vector that should contain top-level `HpoTermId`s used for @@ -1168,20 +1206,6 @@ impl Ontology { Ok(()) } - /// Iterates [`HpoTerm`]s - pub fn iter(&self) -> Iter<'_> { - Iter { - inner: self.hpo_terms.iter(), - ontology: self, - } - } -} - -/// Methods to add annotations -/// -/// These methods should rarely (if ever) be used by clients. -/// Calling these functions might disrupt the Ontology and associated terms. -impl Ontology { /// Crates and inserts a new term to the ontology /// /// This method does not link the term to its parents or to any annotations diff --git a/src/term/hpoterm.rs b/src/term/hpoterm.rs index 99c4864..de9d673 100644 --- a/src/term/hpoterm.rs +++ b/src/term/hpoterm.rs @@ -39,6 +39,9 @@ pub struct HpoTerm<'a> { impl<'a> HpoTerm<'a> { /// Constructs a new [`HpoTerm`] /// + /// Clients should rarely, if ever, use this function. Instead, use the + /// [`Ontology::hpo`] method to get an [`HpoTerm`] + /// /// # Errors /// /// If the given [`HpoTermId`] does not match an existing term @@ -56,6 +59,11 @@ impl<'a> HpoTerm<'a> { /// /// let non_existing_term = HpoTerm::try_new(&ontology, 666666666u32); /// assert!(non_existing_term.is_err()); + /// + /// // better retrieve terms from `Ontology`: + /// + /// let term = ontology.hpo(118u32); + /// assert!(term.is_some()); /// ``` pub fn try_new>(ontology: &'a Ontology, term: I) -> HpoResult> { let term = ontology.get(term).ok_or(HpoError::DoesNotExist)?; diff --git a/src/utils.rs b/src/utils.rs index 979dd3a..f825ba2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -120,4 +120,14 @@ mod test { assert_eq!(c.next(), Some((&4, &4))); assert_eq!(c.next(), None); } + + #[test] + fn combinations_with_none() { + let a = vec![Some(1), Some(2), None, Some(4)]; + let mut c = Combinations::new(&a); + assert_eq!(c.next(), Some((&1, &2))); + assert_eq!(c.next(), Some((&1, &4))); + assert_eq!(c.next(), Some((&2, &4))); + assert_eq!(c.next(), None); + } }