diff --git a/README.md b/README.md index 394a4f4..2209b5d 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ At this time, support for the following ontologies is tested: * Human Phenotype Ontology (HPO) * Gene Ontology (GO) * Medical Action Ontology (MAxO) +* Units of Measurement Ontology (UO) Other ontologies are very likely to work too. In case of any problems, please let us know on our [Issue tracker](https://github.com/P2GX/ontolius/issues). diff --git a/resources/uo/uo.json.gz b/resources/uo/uo.json.gz new file mode 100644 index 0000000..72c3233 Binary files /dev/null and b/resources/uo/uo.json.gz differ diff --git a/src/common.rs b/src/common.rs index 090359a..267f1dd 100644 --- a/src/common.rs +++ b/src/common.rs @@ -41,6 +41,16 @@ pub mod maxo { pub static MEDICAL_ACTION: TermId = make_term_id(KnownPrefix::MAXO, 1, 7); } +/// Constants for working with Unit of Measurement Ontology (UO). +pub mod uo { + use crate::{term_id::KnownPrefix, TermId}; + + use super::make_term_id; + /// [unit (UO:0000000)](http://purl.obolibrary.org/obo/UO_0000000) + /// is the root of all terms in the UO. + pub static UNIT: TermId = make_term_id(KnownPrefix::UO, 0, 7); +} + /// Constants for working with Gene Ontology (GO). pub mod go { use crate::{term_id::KnownPrefix, TermId}; diff --git a/src/term_id.rs b/src/term_id.rs index acfd679..a9efa40 100644 --- a/src/term_id.rs +++ b/src/term_id.rs @@ -459,6 +459,7 @@ pub(crate) enum KnownPrefix { CHEBI, NCIT, PMID, + UO, } impl PartialEq for KnownPrefix { @@ -475,6 +476,7 @@ impl PartialEq for KnownPrefix { KnownPrefix::CHEBI => other == "CHEBI", KnownPrefix::NCIT => other == "NCIT", KnownPrefix::PMID => other == "PMID", + KnownPrefix::UO => other == "UO", } } } @@ -493,6 +495,7 @@ impl Display for KnownPrefix { KnownPrefix::CHEBI => f.write_str("CHEBI"), KnownPrefix::NCIT => f.write_str("NCIT"), KnownPrefix::PMID => f.write_str("PMID"), + KnownPrefix::UO => f.write_str("UO"), } } } @@ -525,7 +528,9 @@ impl TryFrom<&str> for KnownPrefix { Ok(KnownPrefix::NCIT) } else if value.starts_with("PMID") { Ok(KnownPrefix::PMID) - } else { + } else if value.starts_with("UO") { + Ok(KnownPrefix::UO) + }else { Err(()) } } diff --git a/tests/test_common.rs b/tests/test_common.rs index 05b83dc..e52ae44 100644 --- a/tests/test_common.rs +++ b/tests/test_common.rs @@ -1,4 +1,4 @@ -use ontolius::common::{go, hpo, maxo}; +use ontolius::common::{hpo, go, maxo, uo}; #[test] fn hpo_commons_are_accessible() { @@ -18,3 +18,10 @@ fn go_commons_are_accessible() { fn maxo_commons_are_accessible() { assert_eq!(maxo::MEDICAL_ACTION, ("MAXO", "0000001")) } + +#[test] +fn uo_commons_are_accessible() { + assert_eq!(uo::UNIT, ("UO", "0000000")) +} + + diff --git a/tests/test_io.rs b/tests/test_io.rs index 29d4eb8..985f853 100644 --- a/tests/test_io.rs +++ b/tests/test_io.rs @@ -364,3 +364,82 @@ mod medical_action_ontology { _ = maxo.version(); } } + + +/// Unit of Measurement Ontology (UO) tests. +mod unit_measurement_ontology { + + use std::fs::File; + use std::io::BufReader; + use std::sync::OnceLock; + + use flate2::bufread::GzDecoder; + use ontolius::common::uo::UNIT; + use ontolius::io::OntologyLoaderBuilder; + use ontolius::ontology::csr::MinimalCsrOntology; + use ontolius::ontology::{HierarchyWalks, MetadataAware, OntologyTerms}; + use ontolius::term::MinimalTerm; + use ontolius::TermId; + + const UO_PATH: &str = "resources/uo/uo.json.gz"; + + fn uo() -> &'static MinimalCsrOntology { + static ONTOLOGY: OnceLock = OnceLock::new(); + ONTOLOGY.get_or_init(|| { + let reader = GzDecoder::new(BufReader::new( + File::open(UO_PATH).expect("Obographs JSON file should exist"), + )); + let loader = OntologyLoaderBuilder::new().obographs_parser().build(); + loader.load_from_read(reader).expect("Obographs JSON should be well formatted") + }) + } + + macro_rules! test_ancestors { + ($($ontology: expr, $curie: expr, $expected: expr)*) => { + $( + let query: TermId = $curie.parse().unwrap(); + + let mut names: Vec<_> = $ontology + .iter_ancestor_ids(&query) + .map(|tid| $ontology.term_by_id(tid).map(MinimalTerm::name).unwrap()) + .collect(); + names.sort(); + assert_eq!( + names, + $expected, + ); + )* + }; + } + + #[test] + fn iter_ancestor_ids() { + let uo = uo(); + + test_ancestors!( + uo, + "UO:0010002", // millisiemens + &["conduction unit", "electrical conduction unit", "siemens based unit", "unit"] + ); + test_ancestors!( + uo, + "UO:0000010", // second + &["base unit", "second based unit", "time unit", "unit"] + ); + } + + #[test] + fn we_get_expected_descendant_counts_for_uo_root() { + let uo = uo(); + + let descendant_count = uo.iter_descendant_ids(&UNIT).count(); + assert_eq!(descendant_count, 549); + } + + #[test] + fn version_parsing() { + let uo = uo(); + + assert_eq!(uo.version(), "2026-01-09") ; + } +} \ No newline at end of file