Reasoning is the process of inferring implicit knowledge from explicit axioms. owlapy provides three main reasoner implementations, each with different trade-offs.
| Reasoner | Implementation | Completeness | Speed | Dependencies | Use Case |
|---|---|---|---|---|---|
| RDFLibReasoner | Pure Python (SPARQL) | Structural | Fast | rdflib only | General purpose, no circular deps |
| StructuralReasoner | Python (owlready2) | Structural | Very fast | owlready2 | Quick queries, legacy code |
| SyncReasoner | Java (various) | Complete OWL 2 DL | Slower | JPype1 + Java | Full reasoning, complex queries |
Pure Python reasoner using RDFLib and SPARQL queries. No circular dependencies, efficient caching.
from owlapy.owl_ontology import SyncOntology
from owlapy.owl_reasoner_rdflib import RDFLibReasoner
from owlapy.class_expression import OWLClass
# Load ontology
onto = SyncOntology("family.owl")
# Create reasoner
reasoner = RDFLibReasoner(onto)
# Query instances
male = OWLClass("http://example.com/family#Male")
males = list(reasoner.instances(male))
print(f"Found {len(males)} males")✅ No circular dependencies between sub_classes() and super_classes()
✅ Efficient SPARQL queries instead of iterating all classes
✅ Built-in caching for 400x+ speedup on repeated queries
✅ Pure Python - no Java required
✅ Drop-in replacement for StructuralReasoner
# Caching is automatic
first_call = list(reasoner.instances(male)) # ~500ms
second_call = list(reasoner.instances(male)) # ~1ms (cached)
# Pre-warm cache for better performance
reasoner.instances(OWLClass("http://example.com/onto#Person"))- Structural reasoning only (no complex inferencing)
- Does not perform consistency checking
- Does not compute property chains
- Best for instance retrieval and hierarchy navigation
Fast Python reasoner using owlready2. Has circular dependency issue (#205) but still widely used.
from owlapy.owl_reasoner import StructuralReasoner
reasoner = StructuralReasoner(onto)
instances = list(reasoner.instances(male))sub_classes() calls super_classes() and vice versa
Recommendation: Use RDFLibReasoner instead for new code.
Java-based reasoner with complete OWL 2 DL support. Requires JVM lifecycle management.
- HermiT - Complete, handles complex DL, supports SWRL
- Pellet - Complete, good performance, supports SWRL
- JFact - Complete, fast for large TBoxes
- ELK - Incomplete (EL profile only), very fast
- Openllet - Fork of Pellet, actively maintained
from owlapy.owl_reasoner import SyncReasoner
from owlapy.static_funcs import startJVM, stopJVM
# Start JVM (required before using any Java reasoner)
startJVM()
try:
# Create reasoner
reasoner = SyncReasoner(onto, reasoner="HermiT")
# Use reasoner
instances = list(reasoner.instances(male))
# Check consistency
is_consistent = reasoner.has_consistent_ontology()
finally:
# Always stop JVM when done
stopJVM()instances() (and is_entailed(), create_axiom_justifications()) accept a timeout
in seconds (default 1000) and cooperatively cancel the underlying Java reasoning task if
it's exceeded, returning whatever partial results were already found rather than hanging:
instances = list(reasoner.instances(male, timeout=30))startJVM()
reasoner = SyncReasoner(onto, "Pellet")
if reasoner.has_consistent_ontology():
print("Ontology is consistent")
else:
print("Ontology is inconsistent!")
stopJVM()infer_axioms_and_save() is a method on SyncReasoner itself (not a standalone
function) -- it materializes inferred axioms via the OWL API's InferredOntologyGenerator
and saves them onto the reasoner's own ontology.
startJVM()
reasoner = SyncReasoner("family.owl", reasoner="HermiT")
# Infer new class assertions and save to a new file
reasoner.infer_axioms_and_save(
output_path="inferred_family.owl",
output_format="rdfxml",
inference_types=["InferredClassAssertionAxiomGenerator"],
)
stopJVM()Other inference_types include "InferredSubClassAxiomGenerator",
"InferredDisjointClassesAxiomGenerator", "InferredEquivalentClassAxiomGenerator", and
more -- see the method's docstring for the full list. A shortcut for the most common case:
reasoner.generate_and_save_inferred_class_assertion_axioms(output="inferred.ttl").
Justifications are generated directly on the reasoner via create_axiom_justifications()
(there is no get_justifications() on the ontology):
from owlapy.owl_axiom import OWLSubClassOfAxiom
startJVM()
reasoner = SyncReasoner(onto, reasoner="HermiT")
# Why is Student a subclass of Person? (axiom must actually be entailed)
axiom = OWLSubClassOfAxiom(student, person)
justifications = reasoner.create_axiom_justifications(axiom, n_max_justifications=10)
for i, justification in enumerate(justifications, 1):
print(f"Justification {i}:")
for ax in justification:
print(f" - {ax}")
stopJVM()create_laconic_axiom_justifications() has the same signature but returns minimized
("laconic") justifications. Both accept a timeout (seconds, default 1000) for
cooperative cancellation of long-running searches.
# For general use
reasoner = SyncReasoner(onto, "HermiT")
# For better performance with large ontologies
reasoner = SyncReasoner(onto, "JFact")
# For SWRL rule support
reasoner = SyncReasoner(onto, "Pellet")
# For very large EL ontologies (incomplete)
reasoner = SyncReasoner(onto, "ELK")# Get all instances of a class
person = OWLClass("http://example.com/onto#Person")
persons = list(reasoner.instances(person))
# Get instances of complex expression
# Parent ⊓ (∃ hasChild.Male)
complex_expr = OWLObjectIntersectionOf([
parent,
OWLObjectSomeValuesFrom(has_child, male)
])
parents_of_males = list(reasoner.instances(complex_expr))# Get all subclasses (direct and indirect)
all_subs = list(reasoner.sub_classes(person))
# Get only direct subclasses
direct_subs = list(reasoner.sub_classes(person, direct=True))
# Get all superclasses
all_supers = list(reasoner.super_classes(student))
# Get only direct superclasses
direct_supers = list(reasoner.super_classes(student, direct=True))# Get all types of an individual
john = OWLNamedIndividual("http://example.com/onto#John")
types = list(reasoner.types(john))
# Get only direct types
direct_types = list(reasoner.types(john, direct=True))# Get object property values
has_child = OWLObjectProperty("http://example.com/onto#hasChild")
children = list(reasoner.object_property_values(john, has_child))
# Get data property values
has_age = OWLDataProperty("http://example.com/onto#hasAge")
ages = list(reasoner.data_property_values(john, has_age))# equivalent_classes()/disjoint_classes() take a single class expression and return
# everything equivalent/disjoint to it -- check membership to answer "are X and Y ...?"
not_female = OWLObjectComplementOf(female)
are_equiv = male in reasoner.equivalent_classes(not_female)
are_disjoint = female in reasoner.disjoint_classes(male)RDFLibReasoner automatically caches results:
reasoner = RDFLibReasoner(onto)
# First call: ~500ms (builds cache)
males1 = list(reasoner.instances(male))
# Second call: ~1ms (cached)
males2 = list(reasoner.instances(male))# Faster: only direct subclasses
direct = list(reasoner.sub_classes(person, direct=True))
# Slower: all subclasses (recursive traversal)
all_subs = list(reasoner.sub_classes(person, direct=False))classes_to_query = [person, student, teacher, employee]
results = {}
for cls in classes_to_query:
results[cls] = list(reasoner.instances(cls))# For quick instance retrieval
reasoner = RDFLibReasoner(onto) # Fastest
# For complex reasoning
startJVM()
reasoner = SyncReasoner(onto, "HermiT") # Complete but slower
# ... use reasoner ...
stopJVM()from owlapy.owl_ontology import SyncOntology
from owlapy.owl_reasoner_rdflib import RDFLibReasoner
from owlapy.class_expression import *
from owlapy.owl_property import OWLObjectProperty
# Load ontology
onto = SyncOntology("KGs/Family/family-benchmark_rich_background.owl")
reasoner = RDFLibReasoner(onto)
NS = "http://www.benchmark.org/family#"
# Define classes and properties
male = OWLClass(NS + "Male")
female = OWLClass(NS + "Female")
person = OWLClass(NS + "Person")
has_child = OWLObjectProperty(NS + "hasChild")
# Query 1: All males
males = list(reasoner.instances(male))
print(f"Males: {len(males)}")
# Query 2: Parents with daughters
# Person ⊓ (∃ hasChild.Female)
parents_with_daughters = OWLObjectIntersectionOf([
person,
OWLObjectSomeValuesFrom(has_child, female)
])
result = list(reasoner.instances(parents_with_daughters))
print(f"Parents with daughters: {len(result)}")
# Query 3: Class hierarchy
subclasses = list(reasoner.sub_classes(person))
print(f"Subclasses of Person: {[c.str.split('#')[-1] for c in subclasses]}")import time
# RDFLibReasoner (fast)
start = time.time()
reasoner_rdf = RDFLibReasoner(onto)
instances_rdf = list(reasoner_rdf.instances(male))
time_rdf = time.time() - start
print(f"RDFLibReasoner: {len(instances_rdf)} instances in {time_rdf:.3f}s")
# Cached query (very fast)
start = time.time()
instances_cached = list(reasoner_rdf.instances(male))
time_cached = time.time() - start
print(f"Cached: {len(instances_cached)} instances in {time_cached:.6f}s")
print(f"Speedup: {time_rdf / time_cached:.0f}x")from owlapy.static_funcs import startJVM, stopJVM
from owlapy.owl_axiom import OWLSubClassOfAxiom
startJVM()
try:
# Create HermiT reasoner
reasoner = SyncReasoner(onto, "HermiT")
# Check consistency
if not reasoner.has_consistent_ontology():
print("Warning: Ontology is inconsistent!")
# Infer new subclass relationships
teacher = OWLClass(NS + "Teacher")
researcher = OWLClass(NS + "Researcher")
# Check if all teachers are researchers
axiom = OWLSubClassOfAxiom(teacher, researcher)
is_entailed = reasoner.is_entailed(axiom)
print(f"Teacher ⊑ Researcher: {is_entailed}")
# Get explanation
if is_entailed:
justifications = reasoner.get_root_ontology().get_justifications(axiom)
print(f"Found {len(justifications)} justifications")
finally:
stopJVM()Error: JPypeException: JVM already started
Solution:
from owlapy.static_funcs import stopJVM, startJVM
# Stop existing JVM
stopJVM()
# Start fresh
startJVM()Problem: reasoner.instances(cls) returns empty list
Checklist:
- Verify IRI is correct (full namespace + name)
- Check that individuals exist in ontology
- Ensure class is not empty
- For SyncReasoner, verify ontology is consistent
# Debug: print all classes
all_classes = list(onto.classes_in_signature())
print(f"Classes: {[c.str for c in all_classes]}")
# Debug: print all individuals
all_individuals = list(onto.individuals_in_signature())
print(f"Individuals: {[i.str for i in all_individuals]}")Solutions:
- Use RDFLibReasoner instead of StructuralReasoner
- Use
direct=Truewhen you only need direct results - Pre-warm cache with common queries
- Consider NeuralOntology for very large ontologies
# Pre-warm cache
reasoner = RDFLibReasoner(onto)
reasoner.instances(OWLClass(NS + "Person")) # Builds cache- Learn about syntax conversion
- Explore common patterns
- Check API reference