In late 2024, we built a custom OWL 2 RL reasoner directly into Oxigraph — the Rust-based triple store that RDF Studio uses for local storage. The goal was to make reasoning as fast as possible by embedding it at the storage layer, avoiding the overhead of serializing triples out of the database and back in.
The project produced a working prototype: 1,516 lines of Rust implementing forward-chaining OWL 2 RL reasoning with a fixpoint loop. It was tested against the W3C CADE 2011 OWL 2 conformance suite and passed all OWL 2 RL test cases.
This article documents the architecture, what we learned, and how those lessons inform RDF Studio's current reasoner design.
The core is a Rust struct called DeltaReasoner in lib/oxigraph/src/reasoner/delta.rs (808 lines).
DeltaReasoner
lib/oxigraph/src/reasoner/delta.rs
DeltaReasoner { ufds: UnionFind // Canonical forms for owl:sameAs chains list_cache: ListCache // Parsed RDF lists (for intersectionOf, etc.) schema: SchemaIndex // Pre-indexed ontology triples pending: Vec<Quad> // New facts to process }
Union-Find for sameAs: Instead of materializing all owl:sameAs implications (which explode quadratically), the reasoner uses a Union-Find data structure. When A sameAs B and B sameAs C, the Union-Find efficiently computes that A, B, and C are all equivalent without creating explicit triples.
owl:sameAs
A sameAs B
B sameAs C
RDF List Cache: OWL extensively uses RDF lists for constructs like owl:intersectionOf, owl:unionOf, and owl:propertyChainAxiom. Rather than traversing rdf:first/rdf:rest chains repeatedly, the reasoner pre-parses all lists into vectors on initialization.
owl:intersectionOf
owl:unionOf
owl:propertyChainAxiom
rdf:first
rdf:rest
Schema Index: Triples describing classes, properties, and restrictions are indexed separately from instance data. This is similar to how production triplestores separate TBox (schema) from ABox (data).
The reasoner implements 16 named rules organized into two profiles:
Each rule can be independently enabled or disabled via RuleDescriptor and ReasonerConfig.
RuleDescriptor
ReasonerConfig
pub fn reason(&mut self) -> Vec<Quad> { let mut all_new = Vec::new(); loop { let new_facts = self.apply_all_rules(); if new_facts.is_empty() { break; // Fixpoint reached } all_new.extend(new_facts.iter().cloned()); self.pending = new_facts; } all_new }
This is the classic forward-chaining approach: apply all rules, collect new facts, repeat until no new facts are produced. It's the same algorithm used by owlrl, GraphDB, and RDFox — the difference is that it runs at the storage layer with zero serialization overhead.
The test suite referenced as cade2011-schneidsut-owlfullatp comes from a 2011 paper presented at the CADE (Conference on Automated Deduction) workshop:
cade2011-schneidsut-owlfullatp
Schneider, M. & Sutcliffe, G. "Reasoning in OWL 2 Full: an ATP Perspective." CADE-23 Workshop on Automated Reasoning in Quantified Non-Classical Logics (ARQNL), 2011.
The paper proposed a systematic test suite for validating OWL 2 reasoner completeness. It contains:
The test harness in tests.rs (owl2_rl_coverage_suite_runs()) works like this:
tests.rs
owl2_rl_coverage_suite_runs()
The DeltaReasoner passed all OWL 2 RL test cases with 14 known exceptions:
rdf:_1
rdf:_2
owl:hasKey
These exceptions are all explicitly documented in the OWL 2 RL specification as optional or outside the profile's scope.
Both the Oxigraph fork and owlrl use the same fundamental algorithm: apply rules until fixpoint. This validates our choice of owlrl — it's doing the same thing, just in Python instead of Rust.
Performance trade-off: The Rust implementation was ~10x faster for large datasets (millions of triples), but for ontology graphs (hundreds to thousands of triples), the difference is negligible. owlrl processes the Northwind ontology in ~200ms, which is well within interactive range.
The Oxigraph fork's Union-Find approach to owl:sameAs is critical when reasoning over instance data with many equivalences. For ontology-only reasoning (RDF Studio's current use case), this isn't needed — but if we ever add instance-level reasoning, this is the pattern to follow.
Parsing rdf:first/rdf:rest chains is surprisingly expensive when done repeatedly. The fork's ListCache pre-parses them once. owlrl handles this internally, but if we ever hit performance issues with ontologies that have many intersectionOf/unionOf constructs, pre-parsing lists is the optimization to apply.
ListCache
intersectionOf
unionOf
The Oxigraph fork only handles property chains of length 2 (A → B → C). This is a common implementation choice — it covers most real-world cases.
However, the Northwind ontology has a length-3 chain:
nwo:boughtProduct owl:propertyChainAxiom ( [ owl:inverseOf nwo:hasCustomer ] [ owl:inverseOf nwo:belongsToOrder ] nwo:hasProduct ) .
owlrl handles arbitrary-length chains, which is one of its advantages over the custom Rust implementation for RDF Studio's needs.
The fork's SchemaIndex separates ontology triples from instance data, allowing rules to scan only the relevant subset. This is a pattern worth remembering if RDF Studio ever reasons over combined ontology + data graphs.
SchemaIndex
The fork's per-rule enable/disable feature (RuleDescriptor) proved valuable during testing — you could isolate which rule produced unexpected inferences. RDF Studio should consider exposing this in the UI: "Show me only SubClassOf inferences" or "Show me only DisjointWith inferences."
Despite the Oxigraph fork working correctly, we chose owlrl for RDF Studio because:
The 10x performance difference doesn't matter when both are well under 1 second. The operational simplicity of owlrl far outweighs the raw speed advantage.
If RDF Studio ever needs to reason over millions of triples (full instance data, not just ontology schemas), the Oxigraph fork's approach becomes relevant again:
The architecture and rule implementations from the fork are a ready-made blueprint for this future work. The Rust code is documented, tested, and proven against the W3C suite.
For now, owlrl gives us the same logical completeness with dramatically simpler operations. The fork was a valuable research investment that validated our reasoning approach and provided insights we wouldn't have gained otherwise.