An OWL 2 RL reasoner with Z3-verified inference rules.
Overview
Growl implements the OWL 2 RL profile inference rules with Z3-verified contracts on every inference function. Written in SLOP, it compiles to efficient C code while using SMT solving to prove properties about the inference logic.
Growl is being developed as the reasoning engine for Trivyn, a knowledge graph platform.
Features
- Z3-Verified Contracts: Every inference function has machine-checked pre/postconditions; soundness properties prove each output triple is justified by a delta/graph premise (see Verification for scope)
- OWL 2 RL Compliant: Implements W3C specification tables 4-9
- Parallel Execution: Fork-join parallel semi-naive evaluation
- Inconsistency Detection: Reports violations with witness triples
Rule Coverage
76 of 78 OWL 2 RL rules implemented (97% coverage). See detailed coverage table below.
| Table | Rules | Implemented | Description |
|---|---|---|---|
| Table 4 | eq-* | 9/9 | Equality semantics (sameAs, differentFrom) |
| Table 5 | prp-* | 20/20 | Property axioms (domain, range, inverse, transitivity, chains, keys) |
| Table 6 | cls-* | 19/19 | Class expressions (intersectionOf, unionOf, oneOf, qualified cardinality) |
| Table 7 | cax-* | 5/5 | Class axioms (subClassOf, equivalentClass, disjointWith) |
| Table 8 | dt-* | 3/5 | Datatype validation |
| Table 9 | scm-* | 20/20 | Schema vocabulary (class/property hierarchies) |
API
;; Run reasoning with default configuration
(reason arena graph) -> ReasonerResult
;; Run with custom configuration
(reason-with-config arena graph config) -> ReasonerResult
;; Quick consistency check
(is-consistent arena graph) -> Bool
;; Query results
(get-types arena graph individual) -> (List Term)
(get-same-as arena graph individual) -> (List Term)
;; Annotation filtering (preprocessing)
(collect-annotation-properties arena graph) -> (Set Term)
(graph-to-indexed arena graph annot-set) -> IndexedGraph
(indexed-to-graph arena indexed-graph) -> Graph
Configuration
(record ReasonerConfig
(worker-count Int) ;; Number of parallel workers (default: 4)
(channel-buffer Int) ;; Message buffer size (default: 256)
(max-iterations Int) ;; Iteration limit (default: 1000)
(verbose Bool) ;; Print per-iteration timing (default: true)
(fast Bool) ;; Skip schema rules & checks (default: false)
(complete Bool) ;; Enable cls-thing & prp-ap (default: false)
(enrich Bool) ;; Property/subclass enrichment only (default: false)
(validate Bool) ;; Enable TBox validation mode (default: false)
(validate-ns String)) ;; Only validate entities with this IRI prefix (default: "")
CLI Usage
growl [options] <file.ttl> Options: -h, --help Show help message -q, --quiet Only print failures and inconsistencies -f, --fast Skip schema rules and consistency checks -c, --complete Enable cls-thing and prp-ap for spec completeness -e, --enrich Property/subclass enrichment (skip sameAs, class exprs, schema) --validate Check TBox satisfiability via synthetic instance injection --validate-ns NS Only validate entities with IRIs starting with NS -b, --background FILE Load background ontology (e.g. TLO) for reasoning context -o, --emit FILE Write materialized graph to TTL file -V, --version Show version information
Complete Mode
The --complete flag enables axiom rules that are skipped by default for performance:
- cls-thing: Asserts
owl:Thing rdf:type owl:Class - cls-nothing1: Asserts
owl:Nothing rdf:type owl:Class - prp-ap: Asserts standard annotation properties (rdfs:label, rdfs:comment, etc.) as
owl:AnnotationProperty - dt-type2: Asserts
literal rdf:type datatypefor every typed literal with a supported datatype
These rules are spec-correct but produce triples with zero practical inference value. Use --complete for conformance testing against other reasoners like owlrl.
Enrich Mode
The --enrich flag materializes a targeted subset of rules — property characteristics and subclass/subproperty propagation — while skipping sameAs generators, class expressions, and schema materialization. This is useful for practical ontology enrichment without the overhead and triple explosion of full OWL reasoning.
Rules that run in enrich mode:
| Category | Rules | Effect |
|---|---|---|
| Property propagation | prp-dom, prp-rng, prp-spo1, prp-spo2, prp-eqp1/2, prp-inv1/2, prp-symp, prp-trp | Infer types from domains/ranges, propagate through subproperties, inverse, symmetry, transitivity |
| Subclass propagation | cax-sco, cax-eqc1/2 | Propagate rdf:type through subClassOf and equivalentClass |
| Consistency checks | prp-asyp, prp-irp, prp-pdw, prp-adp, prp-npa1/2, cax-dw, cax-adc | Detect disjoint/asymmetric/irreflexive violations |
Rules skipped in enrich mode:
- sameAs generators: prp-fp, prp-ifp, prp-key (these produce
owl:sameAstriples which can cause combinatorial explosion) - All eq-* rules: No sameAs symmetry, transitivity, or replacement
- All cls-* rules: No class expression reasoning (intersectionOf, unionOf, someValuesFrom, hasValue, cardinality, etc.)
- All scm-* rules: No schema materialization (subClassOf/subPropertyOf closure, domain/range propagation through hierarchies)
- Datatype rules: dt-type1, dt-type2, dt-not-type
Enrich mode is a good fit when your ontology already has explicit subClassOf/subPropertyOf chains (most published ontologies do) and you want fast type propagation without the quadratic blowup from sameAs or class expression reasoning.
# Enrich an ontology with property/subclass inferences growl --enrich ontology.ttl # Enrich and emit the materialized graph growl --enrich --emit enriched.ttl ontology.ttl
Validate Mode
The --validate flag enables TBox validation, which checks whether the class and property declarations in an ontology are satisfiable. Growl injects a synthetic blank node instance for each declared class and property, runs reasoning to fixpoint, then scans for inconsistencies triggered by these synthetic individuals.
The following check rules detect unsatisfiable entities: cls-nothing2, cax-dw, cax-adc, cls-com, prp-asyp, prp-pdw. Validate mode reports all unsatisfiable entities, not just the first.
Use --validate-ns to scope validation to a specific namespace. This is important when reasoning over a domain ontology that imports a large top-level ontology (TLO) — without namespace filtering, the TLO's classes would also be validated, which is usually not what you want.
Use --background to load a background ontology that provides reasoning context (e.g. class hierarchy, property declarations) without being validated itself. Background triples participate in inference but are not targets for synthetic instance injection.
Note: --validate overrides --fast, since schema materialization is required for validation to work correctly.
# Validate all classes/properties in an ontology growl --validate ontology.ttl # Validate only entities in a specific namespace growl --validate --validate-ns "https://example.org/" ontology.ttl # Validate a domain ontology against a background TLO growl --validate --validate-ns "https://example.org/" --background tlo.ttl domain.ttl
Fast Mode
The --fast flag skips schema vocabulary rules (scm-*), datatype rules (dt-type1, dt-not-type), equality-difference checks (eq-diff1/2/3), eq-ref, and cardinality rules (cls-maxc1/2, cls-maxqc1-4). Consistency checks (cax-dw, cax-adc, prp-asyp, prp-irp, prp-pdw, prp-adp, prp-npa1/2, cls-nothing2, cls-com) still run in fast mode, matching the coverage of reasoners like reasonable. Most published ontologies already include explicit subClassOf/subPropertyOf chains, making schema closure unnecessary for practical use.
Annotation Filtering
Growl provides annotation filtering as a preprocessing step. Annotation properties have no semantic effect under OWL 2 Direct Semantics — they carry human-readable metadata (labels, comments, descriptions) that cannot trigger any inference rule. Removing them reduces the input graph size without affecting reasoning results.
The following properties are filtered by default:
- OWL/RDFS (9):
rdfs:label,rdfs:comment,rdfs:seeAlso,rdfs:isDefinedBy,owl:deprecated,owl:versionInfo,owl:priorVersion,owl:backwardCompatibleWith,owl:incompatibleWith - SKOS (10):
skos:prefLabel,skos:altLabel,skos:hiddenLabel,skos:definition,skos:note,skos:scopeNote,skos:example,skos:historyNote,skos:editorialNote,skos:changeNote - Dublin Core (8):
dc:title,dc:creator,dc:subject,dc:description,dcterms:title,dcterms:creator,dcterms:description,dcterms:abstract
Additionally, any property declared as owl:AnnotationProperty in the input graph is filtered. This is significant for large ontologies — for example, CCO drops from ~13.6K to ~6K input triples.
Library Usage
;; Build annotation property set from parsed graph
(let ((annot-set (collect-annotation-properties arena g))
(ig (graph-to-indexed arena g annot-set)))
;; Run reasoning on filtered graph
(match (reason arena ig)
((reason-success s)
;; Convert back for serialization, re-adding annotations
(let ((mut out-ig (. s graph)))
(for-each (t (. g triples))
(when (set-has annot-set (. t predicate))
(set! out-ig (indexed-graph-add arena out-ig t))))
(indexed-to-graph arena out-ig)))
((reason-inconsistent _) ...)))
CLI
The CLI applies annotation filtering automatically. When --emit is used, filtered annotation triples are re-added to the output so no data is lost in the materialized graph.
Building
slop build src/growl.slop
Building from C
Pre-transpiled C sources are included in csrc/, so you can build with just a C compiler — no SLOP toolchain required.
make # build cli → build/growl make lib # build static library → build/libgrowl.a make release # optimized build (-O3, NDEBUG) make test # build and run tests make benchmark # run benchmarks make conformance # per-rule + multi-rule interaction tests (92 tests) make reference # accuracy tests against OWL-RL reference reasoner
Rust Bindings
Safe Rust bindings are available in the rust/ directory. See rust/README.md for usage instructions.
Benchmarks
Compared against OWL-RL (Python reference implementation, used as ground truth) and Reasonable (Rust) on 5 real-world ontologies. Growl filters annotation triples before reasoning (see Annotation Filtering), so its effective input is smaller — the "Input" column shows the raw triple count that OWL-RL and Reasonable see.
Performance
| Ontology | Input | OWL-RL | Reasonable | Growl --complete | Growl | Growl --fast |
|---|---|---|---|---|---|---|
| BFO | 1,014 | 583ms | 35ms | 33ms | 21ms | 3ms |
| Pizza | 1,944 | 2.2s | 114ms | 229ms | 164ms | 52ms |
| CCO | 13,649 | 18.4s | 737ms | 3.1s | 2.8s | 434ms |
| Schema.org | 17,823 | 8.3s | 132ms | 691ms | 566ms | 333ms |
| Brick | 53,960 | 33.9s | 286ms | 4.6s | 4.0s | 2.3s |
Accuracy (inferred triples vs OWL-RL reference)
| Ontology | OWL-RL (ref) | Reasonable | Growl --complete | Growl | Growl --fast |
|---|---|---|---|---|---|
| BFO | 2,186 | +91.9% | -0.7% | -17.1% | -83.7% |
| Pizza | 8,005 | +29.1% | -1.9% | -30.6% | -95.2% |
| CCO | 52,363 | -11.4% | +2.7% | -1.8% | -76.0% |
| Schema.org | 26,682 | +15.9% | +0.6% | -0.6% | -40.5% |
| Brick | 39,493 | +81.9% | -0.1% | -23.9% | -69.5% |
Modes: --complete enables all spec rules (cls-thing, prp-ap, dt-type2) for closest OWL-RL parity. Default Growl skips those practically inert rules. --enrich runs only property/subclass propagation and consistency checks (no sameAs, class expressions, or schema materialization). --fast targets the same rule coverage as Reasonable (skips schema rules, datatypes, eq-ref, cardinality), making Growl --fast vs Reasonable an apples-to-apples comparison.
Verification
slop verify # verify all files in [verify].sources slop verify -v # verbose — shows per-function results
Growl uses slop verify, which encodes contracts into Z3 SMT formulas and proves them with weakest-precondition calculus. 104 function-level contracts are verified across the codebase.
What is proven:
- Soundness (
@property soundness): For every output triple, there exists a delta triple justifying it — proving the reasoner never fabricates conclusions from nothing. Covers 56 of 77 inference functions, including all high-value rules (cax-sco, prp-dom/rng, eq-ref/sym, cls-hv1/hv2, etc.). - Completeness (
@property completeness): For certain rules (eq-sym/trans, prp-symp, scm-cls, scm-eqc1/eqc2, scm-eqc-mutual, scm-eqp1/eqp2, scm-eqp-mutual, scm-op, scm-dp), every applicable delta triple produces the expected output — proving the reasoner doesn't miss inferences. 12 functions. - Novelty (
@property novelty): Every output triple is new — not already present in the graph. Proves the rule engine never wastes work emitting redundant inferences. Covers cls-maxc2, cls-maxqc3, cls-maxqc4, and transitive-closure (4 functions). - Postconditions (
@post): Predicate constraints (e.g. all outputs haverdf:typeas predicate), witness counts on inconsistency reports, iteration numbering, and result bounds. - Preconditions (
@pre): Graph size non-negativity, valid input constraints.
What is not proven:
- Soundness for deeply nested callback functions (cls-maxc2/maxqc3/4) that only query the full graph — these don't iterate delta, so the standard delta-sourcing property doesn't apply. They have
@property noveltyand@postpredicate checks instead. - Full join semantics — soundness contracts prove delta-sourcing (every output links to a delta triple) but don't fully encode the multi-way join conditions of rules like cls-int1 (which requires checking all intersection components).
- Termination of the fixed-point loop is not proven (semi-naive evaluation over finite graphs terminates in practice but isn't formally verified).
- Memory safety of the generated C code is not verified (relies on arena allocation patterns).
OWL 2 RL Rule Coverage
Detailed per-rule coverage against the W3C OWL 2 RL specification.
Table 4 — Equality (9/9)
| Rule | Status | Notes |
|---|---|---|
| eq-ref | ✅ | Reflexivity of equality |
| eq-sym | ✅ | Symmetry of owl:sameAs |
| eq-trans | ✅ | Transitivity of owl:sameAs |
| eq-rep-s | ✅ | Replace subject via owl:sameAs |
| eq-rep-p | ✅ | Replace predicate via owl:sameAs |
| eq-rep-o | ✅ | Replace object via owl:sameAs |
| eq-diff1 | ✅ | owl:differentFrom is symmetric |
| eq-diff2 | ✅ | Inconsistency: sameAs and differentFrom |
| eq-diff3 | ✅ | Inconsistency: members of AllDifferent are sameAs |
Table 5 — Property Axioms (20/20)
| Rule | Status | Notes |
|---|---|---|
| prp-ap | ✅ | Annotation property declarations (behind --complete flag) |
| prp-dom | ✅ | rdfs:domain inference |
| prp-rng | ✅ | rdfs:range inference |
| prp-fp | ✅ | Functional property → owl:sameAs |
| prp-ifp | ✅ | Inverse functional property → owl:sameAs |
| prp-irp | ✅ | Irreflexive property inconsistency |
| prp-symp | ✅ | Symmetric property inference |
| prp-asyp | ✅ | Asymmetric property inconsistency |
| prp-trp | ✅ | Transitive property inference |
| prp-spo1 | ✅ | SubPropertyOf inference |
| prp-spo2 | ✅ | Property chain axiom inference |
| prp-eqp1 | ✅ | EquivalentProperty forward |
| prp-eqp2 | ✅ | EquivalentProperty reverse |
| prp-pdw | ✅ | PropertyDisjointWith inconsistency |
| prp-adp | ✅ | AllDisjointProperties inconsistency |
| prp-inv1 | ✅ | InverseOf forward |
| prp-inv2 | ✅ | InverseOf reverse |
| prp-key | ✅ | hasKey → owl:sameAs inference |
| prp-npa1 | ✅ | Negative property assertion (named individual) inconsistency |
| prp-npa2 | ✅ | Negative property assertion (literal) inconsistency |
Table 6 — Class Expressions (19/19)
| Rule | Status | Notes |
|---|---|---|
| cls-thing | ✅ | owl:Thing rdf:type owl:Class (behind --complete flag) |
| cls-nothing1 | ✅ | owl:Nothing rdf:type owl:Class (behind --complete flag) |
| cls-nothing2 | ✅ | Inconsistency: member of owl:Nothing |
| cls-int1 | ✅ | IntersectionOf: member of all → member of intersection |
| cls-int2 | ✅ | IntersectionOf: member of intersection → member of all |
| cls-uni | ✅ | UnionOf: member of component → member of union |
| cls-com | ✅ | ComplementOf inconsistency |
| cls-svf1 | ✅ | SomeValuesFrom (stub — existential, limited in RL) |
| cls-svf2 | ✅ | SomeValuesFrom with owl:Thing |
| cls-avf | ✅ | AllValuesFrom inference |
| cls-hv1 | ✅ | HasValue → type inference |
| cls-hv2 | ✅ | HasValue → property inference |
| cls-maxc1 | ✅ | MaxCardinality 0 (constraint only) |
| cls-maxc2 | ✅ | MaxCardinality 1 → owl:sameAs |
| cls-maxqc1 | ✅ | MaxQualifiedCardinality 0 inconsistency (with onClass) |
| cls-maxqc2 | ✅ | MaxQualifiedCardinality 0 inconsistency (owl:Thing) |
| cls-maxqc3 | ✅ | MaxQualifiedCardinality 1 → owl:sameAs (with onClass) |
| cls-maxqc4 | ✅ | MaxQualifiedCardinality 1 → owl:sameAs (owl:Thing) |
| cls-oo | ✅ | OneOf enumeration |
Table 7 — Class Axioms (5/5)
| Rule | Status | Notes |
|---|---|---|
| cax-sco | ✅ | SubClassOf inference |
| cax-eqc1 | ✅ | EquivalentClass forward |
| cax-eqc2 | ✅ | EquivalentClass reverse |
| cax-dw | ✅ | DisjointWith inconsistency |
| cax-adc | ✅ | AllDisjointClasses inconsistency |
Table 8 — Datatypes (3/5)
| Rule | Status | Notes |
|---|---|---|
| dt-type1 | ✅ | Assert all OWL 2 datatypes as rdfs:Datatype (36 types) |
| dt-type2 | ✅ | Literal typing — asserts literal rdf:type datatype (behind --complete flag) |
| dt-eq | ❌ | Datatype equality — even owlrl skips |
| dt-diff | ❌ | Datatype inequality — even owlrl skips |
| dt-not-type | ✅ | Detect invalid lexical forms (e.g. "abc"^^xsd:integer) |
Table 9 — Schema Vocabulary (20/20)
| Rule | Status | Notes |
|---|---|---|
| scm-cls | ✅ | Class is subclass of itself, owl:Thing; superclass of owl:Nothing |
| scm-sco | ✅ | SubClassOf transitivity |
| scm-eqc1 | ✅ | EquivalentClass → mutual subClassOf |
| scm-eqc2 | ✅ | Mutual subClassOf → equivalentClass |
| scm-op | ✅ | ObjectProperty schema axioms |
| scm-dp | ✅ | DatatypeProperty schema axioms |
| scm-spo | ✅ | SubPropertyOf transitivity |
| scm-eqp1 | ✅ | EquivalentProperty → mutual subPropertyOf |
| scm-eqp2 | ✅ | Mutual subPropertyOf → equivalentProperty |
| scm-dom1 | ✅ | Domain propagation through subClassOf |
| scm-dom2 | ✅ | Domain propagation through subPropertyOf |
| scm-rng1 | ✅ | Range propagation through subClassOf |
| scm-rng2 | ✅ | Range propagation through subPropertyOf |
| scm-hv | ✅ | HasValue + subClassOf interaction |
| scm-svf1 | ✅ | SomeValuesFrom + subClassOf interaction |
| scm-svf2 | ✅ | SomeValuesFrom + subPropertyOf interaction |
| scm-avf1 | ✅ | AllValuesFrom + subClassOf interaction |
| scm-avf2 | ✅ | AllValuesFrom + subPropertyOf interaction |
| scm-int | ✅ | IntersectionOf → subClassOf components |
| scm-uni | ✅ | UnionOf → components subClassOf union |
License
Apache License 2.0 - see LICENSE for details.