1use horned_owl::model::{AnnotatedComponent, Class, SubObjectPropertyExpression};
10use horned_owl::model::{ClassExpression, Component, ForIRI, Individual, ObjectPropertyExpression};
11use horned_owl::ontology::set::SetOntology;
12use thiserror::Error;
13
14use crate::ConceptPool;
15use crate::Vocabulary;
16use crate::ir::{ClassId, ConceptId, IndividualId, Role};
17use crate::ontology::{Axiom, InternalOntology, SubRolePath};
18
19#[derive(Debug, Error, Clone, Eq, PartialEq)]
21pub enum ConversionError {
22 #[error("unsupported class expression variant: {kind}")]
25 UnsupportedConcept { kind: &'static str },
26
27 #[error("unsupported axiom kind: {kind}")]
29 UnsupportedAxiom { kind: &'static str },
30
31 #[error("anonymous individuals are not supported (planned for Phase 7)")]
34 AnonymousIndividual,
35
36 #[error("data ranges and data properties are not supported until Phase 3")]
39 UnsupportedDataRange,
40}
41
42pub fn convert_class_expression<A: ForIRI>(
59 ce: &ClassExpression<A>,
60 vocab: &mut Vocabulary,
61 pool: &mut ConceptPool,
62) -> Result<ConceptId, ConversionError> {
63 match ce {
64 ClassExpression::Class(c) => {
65 let iri: &str = c.0.as_ref();
66 match iri {
73 "http://www.w3.org/2002/07/owl#Thing" => Ok(pool.top()),
74 "http://www.w3.org/2002/07/owl#Nothing" => Ok(pool.bot()),
75 _ => {
76 let class_id = vocab.intern_class(iri);
77 Ok(pool.atomic(class_id))
78 }
79 }
80 }
81 ClassExpression::ObjectIntersectionOf(xs) => {
82 let ids = convert_many(xs, vocab, pool)?;
83 if ids.is_empty() {
84 Ok(pool.top())
85 } else {
86 Ok(pool.and(ids))
87 }
88 }
89 ClassExpression::ObjectUnionOf(xs) => {
90 let ids = convert_many(xs, vocab, pool)?;
91 if ids.is_empty() {
92 Ok(pool.bot())
93 } else {
94 Ok(pool.or(ids))
95 }
96 }
97 ClassExpression::ObjectComplementOf(inner) => {
98 let inner_id = convert_class_expression(inner, vocab, pool)?;
99 Ok(pool.not(inner_id))
100 }
101 ClassExpression::ObjectOneOf(xs) => {
102 let mut ids = Vec::with_capacity(xs.len());
103 for ind in xs {
104 let id = convert_individual(ind, vocab)?;
105 ids.push(pool.nominal(id));
106 }
107 if ids.is_empty() {
108 Ok(pool.bot())
109 } else {
110 Ok(pool.or(ids))
111 }
112 }
113 ClassExpression::ObjectSomeValuesFrom { ope, bce } => {
114 let role = convert_object_property(ope, vocab)?;
115 let inner = convert_class_expression(bce, vocab, pool)?;
116 Ok(pool.some(role, inner))
117 }
118 ClassExpression::ObjectAllValuesFrom { ope, bce } => {
119 let role = convert_object_property(ope, vocab)?;
120 let inner = convert_class_expression(bce, vocab, pool)?;
121 Ok(pool.all(role, inner))
122 }
123 ClassExpression::ObjectHasValue { ope, i } => {
124 let role = convert_object_property(ope, vocab)?;
125 let ind = convert_individual(i, vocab)?;
126 let nom = pool.nominal(ind);
127 Ok(pool.some(role, nom))
128 }
129 ClassExpression::ObjectHasSelf(ope) => {
130 let role = convert_object_property(ope, vocab)?;
131 Ok(pool.self_restriction(role))
132 }
133 ClassExpression::ObjectMinCardinality { n, ope, bce } => {
134 let role = convert_object_property(ope, vocab)?;
135 let inner = convert_class_expression(bce, vocab, pool)?;
136 Ok(pool.min(*n, role, inner))
137 }
138 ClassExpression::ObjectMaxCardinality { n, ope, bce } => {
139 let role = convert_object_property(ope, vocab)?;
140 let inner = convert_class_expression(bce, vocab, pool)?;
141 Ok(pool.max(*n, role, inner))
142 }
143 ClassExpression::ObjectExactCardinality { n, ope, bce } => {
144 let role = convert_object_property(ope, vocab)?;
145 let inner = convert_class_expression(bce, vocab, pool)?;
146 let lo = pool.min(*n, role, inner);
147 let hi = pool.max(*n, role, inner);
148 Ok(pool.and([lo, hi]))
149 }
150 ClassExpression::DataSomeValuesFrom { .. }
151 | ClassExpression::DataAllValuesFrom { .. }
152 | ClassExpression::DataHasValue { .. }
153 | ClassExpression::DataMinCardinality { .. }
154 | ClassExpression::DataMaxCardinality { .. }
155 | ClassExpression::DataExactCardinality { .. } => {
156 Err(ConversionError::UnsupportedDataRange)
157 }
158 }
159}
160
161fn convert_many<A: ForIRI>(
162 xs: &[ClassExpression<A>],
163 vocab: &mut Vocabulary,
164 pool: &mut ConceptPool,
165) -> Result<Vec<ConceptId>, ConversionError> {
166 let mut out = Vec::with_capacity(xs.len());
167 for ce in xs {
168 out.push(convert_class_expression(ce, vocab, pool)?);
169 }
170 Ok(out)
171}
172
173pub fn convert_object_property<A: ForIRI>(
180 ope: &ObjectPropertyExpression<A>,
181 vocab: &mut Vocabulary,
182) -> Result<Role, ConversionError> {
183 match ope {
184 ObjectPropertyExpression::ObjectProperty(op) => {
185 let iri: &str = op.0.as_ref();
186 Ok(Role::named(vocab.intern_role(iri)))
187 }
188 ObjectPropertyExpression::InverseObjectProperty(op) => {
189 let iri: &str = op.0.as_ref();
190 Ok(Role::inverse(vocab.intern_role(iri)))
191 }
192 }
193}
194
195pub fn convert_individual<A: ForIRI>(
197 i: &Individual<A>,
198 vocab: &mut Vocabulary,
199) -> Result<IndividualId, ConversionError> {
200 match i {
201 Individual::Named(ni) => {
202 let iri: &str = ni.0.as_ref();
203 Ok(vocab.intern_individual(iri))
204 }
205 Individual::Anonymous(_) => Err(ConversionError::AnonymousIndividual),
206 }
207}
208
209fn intern_class_decl<A: ForIRI>(c: &Class<A>, vocab: &mut Vocabulary) -> ClassId {
210 let iri: &str = c.0.as_ref();
211 vocab.intern_class(iri)
212}
213
214fn convert_sub_role_path<A: ForIRI>(
215 sub: &SubObjectPropertyExpression<A>,
216 vocab: &mut Vocabulary,
217) -> Result<SubRolePath, ConversionError> {
218 match sub {
219 SubObjectPropertyExpression::ObjectPropertyExpression(ope) => {
220 Ok(SubRolePath::Role(convert_object_property(ope, vocab)?))
221 }
222 SubObjectPropertyExpression::ObjectPropertyChain(chain) => {
223 let mut roles = Vec::with_capacity(chain.len());
224 for link in chain {
225 roles.push(convert_object_property(link, vocab)?);
226 }
227 Ok(SubRolePath::Chain(roles))
228 }
229 }
230}
231
232fn convert_individuals<A: ForIRI>(
233 inds: &[Individual<A>],
234 vocab: &mut Vocabulary,
235) -> Result<Vec<IndividualId>, ConversionError> {
236 let mut out = Vec::with_capacity(inds.len());
237 for i in inds {
238 out.push(convert_individual(i, vocab)?);
239 }
240 Ok(out)
241}
242
243fn convert_roles<A: ForIRI>(
244 opes: &[ObjectPropertyExpression<A>],
245 vocab: &mut Vocabulary,
246) -> Result<Vec<Role>, ConversionError> {
247 let mut out = Vec::with_capacity(opes.len());
248 for o in opes {
249 out.push(convert_object_property(o, vocab)?);
250 }
251 Ok(out)
252}
253
254macro_rules! ce_or_skip {
261 ($expr:expr) => {
262 match $expr {
263 Ok(c) => c,
264 Err(ConversionError::UnsupportedDataRange) => return Ok(None),
265 Err(e) => return Err(e),
266 }
267 };
268}
269
270#[allow(clippy::too_many_lines)] pub fn convert_component<A: ForIRI>(
284 c: &Component<A>,
285 vocab: &mut Vocabulary,
286 pool: &mut ConceptPool,
287) -> Result<Option<Axiom>, ConversionError> {
288 use Component as C;
289 match c {
290 #[allow(clippy::match_same_arms)]
293 C::OntologyID(_)
294 | C::DocIRI(_)
295 | C::OntologyAnnotation(_)
296 | C::Import(_)
297 | C::DeclareAnnotationProperty(_)
298 | C::AnnotationAssertion(_)
299 | C::SubAnnotationPropertyOf(_)
300 | C::AnnotationPropertyDomain(_)
301 | C::AnnotationPropertyRange(_) => Ok(None),
302
303 C::DeclareClass(d) => Ok(Some(Axiom::DeclareClass(intern_class_decl(&d.0, vocab)))),
305 C::DeclareObjectProperty(d) => {
306 let iri: &str = d.0.0.as_ref();
307 Ok(Some(Axiom::DeclareObjectProperty(vocab.intern_role(iri))))
308 }
309 C::DeclareNamedIndividual(d) => {
310 let iri: &str = d.0.0.as_ref();
311 Ok(Some(Axiom::DeclareNamedIndividual(
312 vocab.intern_individual(iri),
313 )))
314 }
315 C::DeclareDataProperty(_) | C::DeclareDatatype(_) => Ok(None),
328
329 C::SubClassOf(ax) => {
331 let sub = ce_or_skip!(convert_class_expression(&ax.sub, vocab, pool));
332 let sup = ce_or_skip!(convert_class_expression(&ax.sup, vocab, pool));
333 Ok(Some(Axiom::SubClassOf { sub, sup }))
334 }
335 C::EquivalentClasses(ax) => {
336 let mut ids = Vec::with_capacity(ax.0.len());
337 for ce in &ax.0 {
338 ids.push(ce_or_skip!(convert_class_expression(ce, vocab, pool)));
339 }
340 Ok(Some(Axiom::EquivalentClasses(ids)))
341 }
342 C::DisjointClasses(ax) => {
343 let mut ids = Vec::with_capacity(ax.0.len());
344 for ce in &ax.0 {
345 ids.push(ce_or_skip!(convert_class_expression(ce, vocab, pool)));
346 }
347 Ok(Some(Axiom::DisjointClasses(ids)))
348 }
349 C::DisjointUnion(ax) => {
350 let class = intern_class_decl(&ax.0, vocab);
351 let mut members = Vec::with_capacity(ax.1.len());
352 for ce in &ax.1 {
353 members.push(ce_or_skip!(convert_class_expression(ce, vocab, pool)));
354 }
355 Ok(Some(Axiom::DisjointUnion { class, members }))
356 }
357
358 C::SubObjectPropertyOf(ax) => {
360 let sub = convert_sub_role_path(&ax.sub, vocab)?;
361 let sup = convert_object_property(&ax.sup, vocab)?;
362 Ok(Some(Axiom::SubObjectPropertyOf { sub, sup }))
363 }
364 C::EquivalentObjectProperties(ax) => {
365 let roles = convert_roles(&ax.0, vocab)?;
366 Ok(Some(Axiom::EquivalentObjectProperties(roles)))
367 }
368 C::DisjointObjectProperties(ax) => {
369 let roles = convert_roles(&ax.0, vocab)?;
370 Ok(Some(Axiom::DisjointObjectProperties(roles)))
371 }
372 C::InverseObjectProperties(ax) => {
373 let a = Role::named(vocab.intern_role(ax.0.0.as_ref()));
374 let b = Role::named(vocab.intern_role(ax.1.0.as_ref()));
375 Ok(Some(Axiom::InverseObjectProperties(a, b)))
376 }
377 C::ObjectPropertyDomain(ax) => {
378 let role = convert_object_property(&ax.ope, vocab)?;
379 let domain = ce_or_skip!(convert_class_expression(&ax.ce, vocab, pool));
380 Ok(Some(Axiom::ObjectPropertyDomain { role, domain }))
381 }
382 C::ObjectPropertyRange(ax) => {
383 let role = convert_object_property(&ax.ope, vocab)?;
384 let range = ce_or_skip!(convert_class_expression(&ax.ce, vocab, pool));
385 Ok(Some(Axiom::ObjectPropertyRange { role, range }))
386 }
387 C::FunctionalObjectProperty(ax) => Ok(Some(Axiom::FunctionalRole(
388 convert_object_property(&ax.0, vocab)?,
389 ))),
390 C::InverseFunctionalObjectProperty(ax) => Ok(Some(Axiom::InverseFunctionalRole(
391 convert_object_property(&ax.0, vocab)?,
392 ))),
393 C::ReflexiveObjectProperty(ax) => Ok(Some(Axiom::ReflexiveRole(convert_object_property(
394 &ax.0, vocab,
395 )?))),
396 C::IrreflexiveObjectProperty(ax) => Ok(Some(Axiom::IrreflexiveRole(
397 convert_object_property(&ax.0, vocab)?,
398 ))),
399 C::SymmetricObjectProperty(ax) => Ok(Some(Axiom::SymmetricRole(convert_object_property(
400 &ax.0, vocab,
401 )?))),
402 C::AsymmetricObjectProperty(ax) => Ok(Some(Axiom::AsymmetricRole(
403 convert_object_property(&ax.0, vocab)?,
404 ))),
405 C::TransitiveObjectProperty(ax) => Ok(Some(Axiom::TransitiveRole(
406 convert_object_property(&ax.0, vocab)?,
407 ))),
408
409 C::ClassAssertion(ax) => {
411 let class = ce_or_skip!(convert_class_expression(&ax.ce, vocab, pool));
412 let individual = convert_individual(&ax.i, vocab)?;
413 Ok(Some(Axiom::ClassAssertion { class, individual }))
414 }
415 C::ObjectPropertyAssertion(ax) => {
416 let role = convert_object_property(&ax.ope, vocab)?;
417 let subject = convert_individual(&ax.from, vocab)?;
418 let object = convert_individual(&ax.to, vocab)?;
419 Ok(Some(Axiom::ObjectPropertyAssertion {
420 role,
421 subject,
422 object,
423 }))
424 }
425 C::NegativeObjectPropertyAssertion(ax) => {
426 let role = convert_object_property(&ax.ope, vocab)?;
427 let subject = convert_individual(&ax.from, vocab)?;
428 let object = convert_individual(&ax.to, vocab)?;
429 Ok(Some(Axiom::NegativeObjectPropertyAssertion {
430 role,
431 subject,
432 object,
433 }))
434 }
435 C::SameIndividual(ax) => Ok(Some(Axiom::SameIndividual(convert_individuals(
436 &ax.0, vocab,
437 )?))),
438 C::DifferentIndividuals(ax) => Ok(Some(Axiom::DifferentIndividuals(convert_individuals(
439 &ax.0, vocab,
440 )?))),
441
442 #[allow(clippy::match_same_arms)]
446 C::SubDataPropertyOf(_)
447 | C::EquivalentDataProperties(_)
448 | C::DisjointDataProperties(_)
449 | C::DataPropertyDomain(_)
450 | C::DataPropertyRange(_)
451 | C::FunctionalDataProperty(_)
452 | C::DatatypeDefinition(_)
453 | C::DataPropertyAssertion(_)
454 | C::NegativeDataPropertyAssertion(_) => Ok(None),
455
456 C::HasKey(_) => Err(ConversionError::UnsupportedAxiom { kind: "HasKey" }),
458
459 #[allow(clippy::match_same_arms)]
469 C::Rule(_) => Ok(None),
470 }
471}
472
473pub fn convert_ontology<A: ForIRI>(
494 src: &SetOntology<A>,
495) -> Result<InternalOntology, ConversionError> {
496 let mut components: Vec<&AnnotatedComponent<A>> = src.iter().collect();
497 components.sort();
498 let mut out = InternalOntology::new();
499 for ac in components {
500 if let Some(axiom) =
501 convert_component(&ac.component, &mut out.vocabulary, &mut out.concepts)?
502 {
503 out.axioms.push(axiom);
504 }
505 }
506 let bot_id = out.concepts.bot();
518 let derived = {
524 let concepts_cell = std::cell::RefCell::new(&mut out.concepts);
525 crate::data_axioms::derive_data_axioms(
526 src,
527 &out.vocabulary,
528 bot_id,
529 |cid| concepts_cell.borrow_mut().atomic(cid),
530 )
531 };
532 out.axioms.extend(derived);
533 out.axioms.sort();
534 Ok(out)
535}
536
537impl<A: ForIRI> TryFrom<&SetOntology<A>> for InternalOntology {
538 type Error = ConversionError;
539
540 fn try_from(src: &SetOntology<A>) -> Result<Self, Self::Error> {
541 convert_ontology(src)
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 #![allow(clippy::unwrap_used)]
548
549 use super::*;
550 use crate::ir::ConceptExpr;
551 use horned_owl::model::{Build, RcStr};
552
553 fn b() -> Build<RcStr> {
554 Build::new_rc()
555 }
556
557 fn ctx() -> (Vocabulary, ConceptPool) {
558 (Vocabulary::new(), ConceptPool::new())
559 }
560
561 #[test]
562 fn class() {
563 let (mut v, mut p) = ctx();
564 let ce = ClassExpression::Class(b().class("http://example.org/A"));
565 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
566 let ConceptExpr::Atomic(c) = p.get(id) else {
567 panic!("expected Atomic")
568 };
569 assert_eq!(v.class_iri(*c), "http://example.org/A");
570 }
571
572 #[test]
573 fn intersection() {
574 let (mut v, mut p) = ctx();
575 let ce = ClassExpression::ObjectIntersectionOf(vec![
576 ClassExpression::Class(b().class("A")),
577 ClassExpression::Class(b().class("B")),
578 ]);
579 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
580 let ConceptExpr::And(args) = p.get(id) else {
581 panic!("expected And")
582 };
583 assert_eq!(args.len(), 2);
584 }
585
586 #[test]
587 fn empty_intersection_is_top() {
588 let (mut v, mut p) = ctx();
589 let ce: ClassExpression<RcStr> = ClassExpression::ObjectIntersectionOf(vec![]);
590 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
591 assert!(matches!(p.get(id), ConceptExpr::Top));
592 }
593
594 #[test]
595 fn union() {
596 let (mut v, mut p) = ctx();
597 let ce = ClassExpression::ObjectUnionOf(vec![
598 ClassExpression::Class(b().class("A")),
599 ClassExpression::Class(b().class("B")),
600 ]);
601 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
602 assert!(matches!(p.get(id), ConceptExpr::Or(_)));
603 }
604
605 #[test]
606 fn empty_union_is_bot() {
607 let (mut v, mut p) = ctx();
608 let ce: ClassExpression<RcStr> = ClassExpression::ObjectUnionOf(vec![]);
609 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
610 assert!(matches!(p.get(id), ConceptExpr::Bot));
611 }
612
613 #[test]
614 fn complement() {
615 let (mut v, mut p) = ctx();
616 let ce =
617 ClassExpression::ObjectComplementOf(Box::new(ClassExpression::Class(b().class("A"))));
618 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
619 assert!(matches!(p.get(id), ConceptExpr::Not(_)));
620 }
621
622 #[test]
623 fn some_values_from() {
624 let (mut v, mut p) = ctx();
625 let ce = ClassExpression::ObjectSomeValuesFrom {
626 ope: ObjectPropertyExpression::ObjectProperty(b().object_property("r")),
627 bce: Box::new(ClassExpression::Class(b().class("A"))),
628 };
629 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
630 assert!(matches!(p.get(id), ConceptExpr::Some(_, _)));
631 }
632
633 #[test]
634 fn all_values_from() {
635 let (mut v, mut p) = ctx();
636 let ce = ClassExpression::ObjectAllValuesFrom {
637 ope: ObjectPropertyExpression::ObjectProperty(b().object_property("r")),
638 bce: Box::new(ClassExpression::Class(b().class("A"))),
639 };
640 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
641 assert!(matches!(p.get(id), ConceptExpr::All(_, _)));
642 }
643
644 #[test]
645 fn has_value_encodes_as_some_of_nominal() {
646 let (mut v, mut p) = ctx();
647 let ce = ClassExpression::ObjectHasValue {
648 ope: ObjectPropertyExpression::ObjectProperty(b().object_property("r")),
649 i: Individual::Named(b().named_individual("a")),
650 };
651 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
652 let ConceptExpr::Some(_, inner) = p.get(id) else {
653 panic!("expected Some(_, _)")
654 };
655 assert!(matches!(p.get(*inner), ConceptExpr::Nominal(_)));
656 }
657
658 #[test]
659 fn has_self() {
660 let (mut v, mut p) = ctx();
661 let ce = ClassExpression::ObjectHasSelf(ObjectPropertyExpression::ObjectProperty(
662 b().object_property("r"),
663 ));
664 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
665 assert!(matches!(p.get(id), ConceptExpr::SelfRestriction(_)));
666 }
667
668 #[test]
669 fn min_cardinality() {
670 let (mut v, mut p) = ctx();
671 let ce = ClassExpression::ObjectMinCardinality {
672 n: 3,
673 ope: ObjectPropertyExpression::ObjectProperty(b().object_property("r")),
674 bce: Box::new(ClassExpression::Class(b().class("A"))),
675 };
676 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
677 let ConceptExpr::Min(n, _, _) = p.get(id) else {
678 panic!("expected Min")
679 };
680 assert_eq!(*n, 3);
681 }
682
683 #[test]
684 fn max_cardinality() {
685 let (mut v, mut p) = ctx();
686 let ce = ClassExpression::ObjectMaxCardinality {
687 n: 5,
688 ope: ObjectPropertyExpression::ObjectProperty(b().object_property("r")),
689 bce: Box::new(ClassExpression::Class(b().class("A"))),
690 };
691 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
692 let ConceptExpr::Max(n, _, _) = p.get(id) else {
693 panic!("expected Max")
694 };
695 assert_eq!(*n, 5);
696 }
697
698 #[test]
699 fn exact_cardinality_encodes_as_and_of_min_max() {
700 let (mut v, mut p) = ctx();
701 let ce = ClassExpression::ObjectExactCardinality {
702 n: 2,
703 ope: ObjectPropertyExpression::ObjectProperty(b().object_property("r")),
704 bce: Box::new(ClassExpression::Class(b().class("A"))),
705 };
706 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
707 let ConceptExpr::And(args) = p.get(id) else {
708 panic!("expected And(Min, Max)")
709 };
710 assert_eq!(args.len(), 2);
711 let kinds: Vec<&'static str> = args
713 .iter()
714 .map(|a| match p.get(*a) {
715 ConceptExpr::Min(..) => "Min",
716 ConceptExpr::Max(..) => "Max",
717 _ => "other",
718 })
719 .collect();
720 assert!(kinds.contains(&"Min"));
721 assert!(kinds.contains(&"Max"));
722 }
723
724 #[test]
725 fn one_of_encodes_as_or_of_nominals() {
726 let (mut v, mut p) = ctx();
727 let ce = ClassExpression::ObjectOneOf(vec![
728 Individual::Named(b().named_individual("a")),
729 Individual::Named(b().named_individual("b")),
730 ]);
731 let id = convert_class_expression(&ce, &mut v, &mut p).unwrap();
732 let ConceptExpr::Or(args) = p.get(id) else {
733 panic!("expected Or")
734 };
735 assert_eq!(args.len(), 2);
736 for a in args {
737 assert!(matches!(p.get(*a), ConceptExpr::Nominal(_)));
738 }
739 }
740
741 #[test]
742 fn inverse_object_property_lowers_to_inverse_role() {
743 let mut v = Vocabulary::new();
744 let ope =
745 ObjectPropertyExpression::<RcStr>::InverseObjectProperty(b().object_property("r"));
746 let role = convert_object_property(&ope, &mut v).unwrap();
747 assert!(role.is_inverse());
748 let forward = b().object_property("r");
750 let forward_ope = ObjectPropertyExpression::<RcStr>::ObjectProperty(forward);
751 let forward_role = convert_object_property(&forward_ope, &mut v).unwrap();
752 assert_eq!(role.role_id(), forward_role.role_id());
753 }
754
755 #[test]
756 fn anonymous_individual_rejected() {
757 use horned_owl::model::AnonymousIndividual;
758 use std::rc::Rc;
759
760 let mut v = Vocabulary::new();
761 let i = Individual::<RcStr>::Anonymous(AnonymousIndividual(Rc::from("blank-0")));
762 let err = convert_individual(&i, &mut v).unwrap_err();
763 assert_eq!(err, ConversionError::AnonymousIndividual);
764 }
765
766 #[test]
767 fn data_some_values_rejected() {
768 let (mut v, mut p) = ctx();
769 let ce: ClassExpression<RcStr> = ClassExpression::DataSomeValuesFrom {
770 dp: b().data_property("dp"),
771 dr: horned_owl::model::DataRange::Datatype(b().datatype("dt")),
772 };
773 let err = convert_class_expression(&ce, &mut v, &mut p).unwrap_err();
774 assert_eq!(err, ConversionError::UnsupportedDataRange);
775 }
776
777 #[test]
778 fn shared_subexpressions_share_ids() {
779 let (mut v, mut p) = ctx();
780 let ce1 = ClassExpression::Class(b().class("A"));
781 let ce2 = ClassExpression::Class(b().class("A"));
782 let id1 = convert_class_expression(&ce1, &mut v, &mut p).unwrap();
783 let id2 = convert_class_expression(&ce2, &mut v, &mut p).unwrap();
784 assert_eq!(id1, id2);
785 assert_eq!(p.len(), 1);
786 assert_eq!(v.num_classes(), 1);
787 }
788
789 use horned_owl::model as ho;
794 use horned_owl::model::MutableOntology;
795
796 fn ce_class(name: &str) -> ClassExpression<RcStr> {
797 ClassExpression::Class(b().class(name))
798 }
799
800 fn ope(name: &str) -> ObjectPropertyExpression<RcStr> {
801 ObjectPropertyExpression::ObjectProperty(b().object_property(name))
802 }
803
804 fn named_ind(name: &str) -> Individual<RcStr> {
805 Individual::Named(b().named_individual(name))
806 }
807
808 fn convert_one(c: &Component<RcStr>) -> (InternalOntology, Option<Axiom>) {
809 let mut o = InternalOntology::new();
810 let ax = convert_component(c, &mut o.vocabulary, &mut o.concepts).unwrap();
811 (o, ax)
812 }
813
814 #[test]
815 fn sub_class_of_axiom() {
816 let c = Component::SubClassOf(ho::SubClassOf {
817 sub: ce_class("A"),
818 sup: ce_class("B"),
819 });
820 let (_, ax) = convert_one(&c);
821 assert!(matches!(ax, Some(Axiom::SubClassOf { .. })));
822 }
823
824 #[test]
825 fn equivalent_classes_axiom_keeps_vec() {
826 let c = Component::EquivalentClasses(ho::EquivalentClasses(vec![
827 ce_class("A"),
828 ce_class("B"),
829 ce_class("C"),
830 ]));
831 let (_, ax) = convert_one(&c);
832 let Some(Axiom::EquivalentClasses(v)) = ax else {
833 panic!()
834 };
835 assert_eq!(v.len(), 3);
836 }
837
838 #[test]
839 fn disjoint_classes_axiom() {
840 let c = Component::DisjointClasses(ho::DisjointClasses(vec![ce_class("A"), ce_class("B")]));
841 let (_, ax) = convert_one(&c);
842 assert!(matches!(ax, Some(Axiom::DisjointClasses(_))));
843 }
844
845 #[test]
846 fn disjoint_union_axiom() {
847 let c = Component::DisjointUnion(ho::DisjointUnion(
848 b().class("Parent"),
849 vec![ce_class("Child1"), ce_class("Child2")],
850 ));
851 let (_, ax) = convert_one(&c);
852 let Some(Axiom::DisjointUnion { members, .. }) = ax else {
853 panic!()
854 };
855 assert_eq!(members.len(), 2);
856 }
857
858 #[test]
859 fn sub_object_property_of_single() {
860 let c = Component::SubObjectPropertyOf(ho::SubObjectPropertyOf {
861 sub: SubObjectPropertyExpression::ObjectPropertyExpression(ope("r")),
862 sup: ope("s"),
863 });
864 let (_, ax) = convert_one(&c);
865 let Some(Axiom::SubObjectPropertyOf { sub, .. }) = ax else {
866 panic!()
867 };
868 assert!(matches!(sub, SubRolePath::Role(_)));
869 }
870
871 #[test]
872 fn sub_object_property_of_chain() {
873 let c = Component::SubObjectPropertyOf(ho::SubObjectPropertyOf {
874 sub: SubObjectPropertyExpression::ObjectPropertyChain(vec![ope("r"), ope("s")]),
875 sup: ope("t"),
876 });
877 let (_, ax) = convert_one(&c);
878 let Some(Axiom::SubObjectPropertyOf { sub, .. }) = ax else {
879 panic!()
880 };
881 let SubRolePath::Chain(chain) = sub else {
882 panic!()
883 };
884 assert_eq!(chain.len(), 2);
885 }
886
887 #[test]
888 fn equivalent_object_properties() {
889 let c = Component::EquivalentObjectProperties(ho::EquivalentObjectProperties(vec![
890 ope("r"),
891 ope("s"),
892 ]));
893 let (_, ax) = convert_one(&c);
894 assert!(matches!(ax, Some(Axiom::EquivalentObjectProperties(_))));
895 }
896
897 #[test]
898 fn inverse_object_properties_axiom() {
899 let c = Component::InverseObjectProperties(ho::InverseObjectProperties(
900 b().object_property("r"),
901 b().object_property("s"),
902 ));
903 let (_, ax) = convert_one(&c);
904 assert!(matches!(ax, Some(Axiom::InverseObjectProperties(_, _))));
905 }
906
907 #[test]
908 fn object_property_domain_and_range() {
909 let domain_c = Component::ObjectPropertyDomain(ho::ObjectPropertyDomain {
910 ope: ope("r"),
911 ce: ce_class("A"),
912 });
913 let range_c = Component::ObjectPropertyRange(ho::ObjectPropertyRange {
914 ope: ope("r"),
915 ce: ce_class("B"),
916 });
917 assert!(matches!(
918 convert_one(&domain_c).1,
919 Some(Axiom::ObjectPropertyDomain { .. })
920 ));
921 assert!(matches!(
922 convert_one(&range_c).1,
923 Some(Axiom::ObjectPropertyRange { .. })
924 ));
925 }
926
927 #[test]
928 fn role_characteristics() {
929 type AxiomCheck = (Component<RcStr>, fn(&Axiom) -> bool);
930 let cases: Vec<AxiomCheck> = vec![
931 (
932 Component::TransitiveObjectProperty(ho::TransitiveObjectProperty(ope("r"))),
933 |a| matches!(a, Axiom::TransitiveRole(_)),
934 ),
935 (
936 Component::FunctionalObjectProperty(ho::FunctionalObjectProperty(ope("r"))),
937 |a| matches!(a, Axiom::FunctionalRole(_)),
938 ),
939 (
940 Component::InverseFunctionalObjectProperty(ho::InverseFunctionalObjectProperty(
941 ope("r"),
942 )),
943 |a| matches!(a, Axiom::InverseFunctionalRole(_)),
944 ),
945 (
946 Component::ReflexiveObjectProperty(ho::ReflexiveObjectProperty(ope("r"))),
947 |a| matches!(a, Axiom::ReflexiveRole(_)),
948 ),
949 (
950 Component::IrreflexiveObjectProperty(ho::IrreflexiveObjectProperty(ope("r"))),
951 |a| matches!(a, Axiom::IrreflexiveRole(_)),
952 ),
953 (
954 Component::SymmetricObjectProperty(ho::SymmetricObjectProperty(ope("r"))),
955 |a| matches!(a, Axiom::SymmetricRole(_)),
956 ),
957 (
958 Component::AsymmetricObjectProperty(ho::AsymmetricObjectProperty(ope("r"))),
959 |a| matches!(a, Axiom::AsymmetricRole(_)),
960 ),
961 ];
962 for (c, pred) in cases {
963 let (_, ax) = convert_one(&c);
964 let ax = ax.expect("expected an axiom");
965 assert!(pred(&ax), "wrong axiom: {ax:?}");
966 }
967 }
968
969 #[test]
970 fn class_assertion() {
971 let c = Component::ClassAssertion(ho::ClassAssertion {
972 ce: ce_class("A"),
973 i: named_ind("a"),
974 });
975 let (_, ax) = convert_one(&c);
976 assert!(matches!(ax, Some(Axiom::ClassAssertion { .. })));
977 }
978
979 #[test]
980 fn object_property_assertion_positive_and_negative() {
981 let pos = Component::ObjectPropertyAssertion(ho::ObjectPropertyAssertion {
982 ope: ope("r"),
983 from: named_ind("a"),
984 to: named_ind("b"),
985 });
986 let neg = Component::NegativeObjectPropertyAssertion(ho::NegativeObjectPropertyAssertion {
987 ope: ope("r"),
988 from: named_ind("a"),
989 to: named_ind("b"),
990 });
991 assert!(matches!(
992 convert_one(&pos).1,
993 Some(Axiom::ObjectPropertyAssertion { .. })
994 ));
995 assert!(matches!(
996 convert_one(&neg).1,
997 Some(Axiom::NegativeObjectPropertyAssertion { .. })
998 ));
999 }
1000
1001 #[test]
1002 fn same_and_different_individuals() {
1003 let same =
1004 Component::SameIndividual(ho::SameIndividual(vec![named_ind("a"), named_ind("b")]));
1005 let diff = Component::DifferentIndividuals(ho::DifferentIndividuals(vec![
1006 named_ind("a"),
1007 named_ind("c"),
1008 ]));
1009 assert!(matches!(
1010 convert_one(&same).1,
1011 Some(Axiom::SameIndividual(_))
1012 ));
1013 assert!(matches!(
1014 convert_one(&diff).1,
1015 Some(Axiom::DifferentIndividuals(_))
1016 ));
1017 }
1018
1019 #[test]
1020 fn declarations() {
1021 assert!(matches!(
1022 convert_one(&Component::DeclareClass(ho::DeclareClass(b().class("A")))).1,
1023 Some(Axiom::DeclareClass(_))
1024 ));
1025 assert!(matches!(
1026 convert_one(&Component::DeclareObjectProperty(
1027 ho::DeclareObjectProperty(b().object_property("r"))
1028 ))
1029 .1,
1030 Some(Axiom::DeclareObjectProperty(_))
1031 ));
1032 assert!(matches!(
1033 convert_one(&Component::DeclareNamedIndividual(
1034 ho::DeclareNamedIndividual(b().named_individual("a"))
1035 ))
1036 .1,
1037 Some(Axiom::DeclareNamedIndividual(_))
1038 ));
1039 }
1040
1041 #[test]
1042 fn metadata_and_annotations_silently_skipped() {
1043 let id = ho::OntologyID::default();
1045 let (_, ax) = convert_one(&Component::<RcStr>::OntologyID(id));
1046 assert!(ax.is_none());
1047 let ap = Component::<RcStr>::DeclareAnnotationProperty(ho::DeclareAnnotationProperty(
1049 b().annotation_property("p"),
1050 ));
1051 assert!(convert_one(&ap).1.is_none());
1052 }
1053
1054 #[test]
1060 fn data_axiom_declarations_silently_dropped() {
1061 let c = Component::<RcStr>::DeclareDataProperty(ho::DeclareDataProperty(
1062 b().data_property("dp"),
1063 ));
1064 let mut o = InternalOntology::new();
1065 let result = convert_component(&c, &mut o.vocabulary, &mut o.concepts).unwrap();
1066 assert!(
1067 result.is_none(),
1068 "Phase D1: data-property declarations drop silently (Ok(None))"
1069 );
1070 }
1071
1072 #[test]
1078 fn subclass_with_data_range_silently_dropped() {
1079 use horned_owl::model::DataProperty;
1080 let dp = DataProperty::<RcStr>(b().iri("http://t/dp"));
1081 let c = Component::<RcStr>::SubClassOf(ho::SubClassOf {
1082 sub: ce_class("A"),
1083 sup: ClassExpression::DataMaxCardinality {
1084 n: 1,
1085 dp,
1086 dr: horned_owl::model::DataRange::Datatype(horned_owl::model::Datatype(
1087 b().iri("http://www.w3.org/2001/XMLSchema#integer"),
1088 )),
1089 },
1090 });
1091 let mut o = InternalOntology::new();
1092 let result = convert_component(&c, &mut o.vocabulary, &mut o.concepts).unwrap();
1093 assert!(
1094 result.is_none(),
1095 "Phase D1: SubClassOf containing a data-range SUP drops silently"
1096 );
1097 }
1098
1099 #[test]
1100 fn convert_ontology_smoke() {
1101 let mut o = SetOntology::<RcStr>::new();
1102 o.insert(ho::AnnotatedComponent::from(Component::SubClassOf(
1103 ho::SubClassOf {
1104 sub: ce_class("A"),
1105 sup: ce_class("B"),
1106 },
1107 )));
1108 o.insert(ho::AnnotatedComponent::from(Component::DeclareClass(
1109 ho::DeclareClass(b().class("A")),
1110 )));
1111 let internal = convert_ontology(&o).unwrap();
1112 assert_eq!(internal.num_axioms(), 2);
1113 assert_eq!(internal.vocabulary.num_classes(), 2); }
1115
1116 #[test]
1117 fn try_from_set_ontology() {
1118 let mut o = SetOntology::<RcStr>::new();
1119 o.insert(ho::AnnotatedComponent::from(Component::SubClassOf(
1120 ho::SubClassOf {
1121 sub: ce_class("A"),
1122 sup: ce_class("B"),
1123 },
1124 )));
1125 let internal = InternalOntology::try_from(&o).unwrap();
1126 assert_eq!(internal.num_axioms(), 1);
1127 }
1128}