CLI Reference ************* The "reasonable" binary loads one or more RDF files, runs OWL 2 RL materialisation, and writes the result to disk. Install ======= cargo install reasonable-cli # or build from the workspace after cloning: cargo build -p reasonable-cli --release ./target/release/reasonable --help Usage ===== reasonable [OPTIONS] ... Arguments: ... One or more Turtle or N3 input files Options: -o, --output-file Output file [default: output.ttl] --error-format Diagnostic output format: text | json | ndjson [default: text] --fail-on Exit with code 2 if any of these rule codes occur --max-diagnostics Limit the number of diagnostics printed --summary-only Print only a count of diagnostics, not individual messages -h, --help Print help -V, --version Print version Basic example ============= reasonable Brick.n3 building_model.n3 -o result.ttl All input files are merged into a single graph before reasoning begins. Diagnostics =========== The reasoner records OWL 2 RL rule violations as *diagnostics* rather than aborting. By default they are printed to stdout in plain text after the output file is written. Change the format with "--error-format": # machine-readable JSON array reasonable model.ttl --error-format json -o out.ttl # one JSON object per line (newline-delimited) reasonable model.ttl --error-format ndjson -o out.ttl Each diagnostic has four fields: "code", "rule", "severity", and "message". Failing on specific violations ============================== Use "--fail-on" to exit with code **2** when a particular rule violation is detected. Pass rule codes as a comma-separated list or repeat the flag: reasonable model.ttl --fail-on cax-dw,prp-pdw -o out.ttl # codes are case-insensitive; OWLRL.* prefix is also accepted reasonable model.ttl --fail-on OWLRL.CAX_DW -o out.ttl Common diagnostic codes ======================= +---------------------------+-----------------------------------------------------------------------------+ | Code | Meaning | |===========================|=============================================================================| | "OWLRL.CAX_DW" | Individual typed as two disjoint classes | +---------------------------+-----------------------------------------------------------------------------+ | "OWLRL.PRP_PDW" | Pair of individuals violates "owl:propertyDisjointWith" | +---------------------------+-----------------------------------------------------------------------------+ | "OWLRL.PRP_ASYP" | Asymmetric property asserted in both directions | +---------------------------+-----------------------------------------------------------------------------+ | "OWLRL.PRP_IRP" | Irreflexive property used with the same subject and object | +---------------------------+-----------------------------------------------------------------------------+ | "OWLRL.CLS_NOTHING" | Individual typed as "owl:Nothing" | +---------------------------+-----------------------------------------------------------------------------+ Limiting output =============== # print at most 10 diagnostics reasonable model.ttl --max-diagnostics 10 -o out.ttl # print only the total count, not individual messages reasonable model.ttl --summary-only -o out.ttl Environment =========== Set "RUST_LOG" to control log verbosity: RUST_LOG=debug reasonable model.ttl -o out.ttl The default level is "info", which logs file load times and the total reasoning duration. Getting Started *************** Install ======= **Python package** (pre-built wheels, no Rust toolchain required): pip install reasonable # Python 3.9+ **CLI binary** (requires a Rust toolchain): cargo install reasonable-cli # or build from source after cloning: cargo build -p reasonable-cli --release # binary lands at: ./target/release/reasonable Basic usage =========== Pass one or more Turtle or N3 files to the "reasonable" binary. The reasoner loads all triples, runs OWL 2 RL materialisation, then writes the result to "output.ttl" (override with "-o"): reasonable ontology.ttl data.ttl -o result.ttl You can mix as many input files as needed. The reasoner reads each file in order and treats all triples as a single combined graph. Python quickstart ================= Load from files on disk: import reasonable r = reasonable.PyReasoner() r.load_file("ontology.ttl") r.load_file("data.ttl") triples = r.reason() # list of (subject, predicate, object) rdflib nodes print(len(triples)) Load from an existing rdflib graph: import rdflib import reasonable g = rdflib.Graph() g.parse("ontology.ttl") g.parse("data.ttl") r = reasonable.PyReasoner() r.from_graph(g) triples = r.reason() # collect into a new graph result = rdflib.Graph() for triple in triples: result.add(triple) Incremental reasoning ===================== After the first "reason()" call the reasoner tracks which triples it has already processed. Calling "reason()" again is incremental — only newly added triples are processed. Use "update_graph()" when your data changes. It replaces the base triples and automatically picks incremental or full re- materialisation: r = reasonable.PyReasoner() r.from_graph(ontology + initial_data) r.reason() # full materialisation # data changes over time… new_data.add(triple_a) new_data.remove(triple_b) removed = r.update_graph(ontology + new_data) r.reason() # full re-mat when removed=True, else incremental "update_graph()" returns "True" when removals were detected (which forces a full re-materialisation on the next "reason()" call) and "False" otherwise. Building from source ==================== A Rust toolchain (via rustup) is required for everything; uv and Python 3.9+ are additionally needed for the Python bindings. make build # release CLI binary make test # Rust test suite make dev-python-library # build Python extension into python/.venv make test-python # build + run pytest make build-python-library # distributable wheel → python/dist/ Building the docs ================= cd docs uv sync uv run sphinx-build -M html . _build open _build/html/index.html Reasonable ********** [image: PyPI version][image][image: Crates.io version for reasonable][image][image: Crates.io version for reasonable- cli][image][image: docs.rs][image][image: BSD-3-Clause license][image] An OWL 2 RL reasoner with reasonable performance. Reasonable materialises the deductive closure of an RDF graph under the OWL 2 RL profile. It is implemented in Rust and exposed through a command-line tool and Python bindings. +----------------------------------------------------+----------------------------------------------------+ | PyPI | "pip install reasonable" | +----------------------------------------------------+----------------------------------------------------+ | Cargo (CLI) | "cargo install reasonable-cli" | +----------------------------------------------------+----------------------------------------------------+ | GitHub | https://github.com/gtfierro/reasonable | +----------------------------------------------------+----------------------------------------------------+ | License | BSD-3-Clause | +----------------------------------------------------+----------------------------------------------------+ Performance =========== Benchmarked on Brick models of varying sizes, Reasonable is roughly 7× faster than Allegro and 38× faster than OWLRL. Quick start =========== **CLI** — load one or more Turtle/N3 files and write the materialised output: reasonable ontology.ttl data.ttl -o output.ttl **Python** — reason over an rdflib graph: import rdflib import reasonable g = rdflib.Graph() g.parse("ontology.ttl") g.parse("data.ttl") r = reasonable.PyReasoner() r.from_graph(g) triples = r.reason() Contents ======== * Getting Started * Install * Basic usage * Python quickstart * Incremental reasoning * Building from source * Building the docs * Python API * PyReasoner * Examples * Building from source * CLI Reference * Install * Usage * Basic example * Diagnostics * Failing on specific violations * Common diagnostic codes * Limiting output * Environment * OWL 2 RL Rules * RDFS Semantics * Equality Semantics * Property Axiom Semantics * Class Semantics * Class Axiom Semantics * Schema Vocabulary Semantics * Not yet implemented * Rust API (docs.rs) OWL 2 RL Rules ************** Reasonable implements a subset of the OWL 2 RL reasoning rules defined by the W3C. The tables below show the current implementation status. +----------------------------------------------------+----------------------------------------------------+ | supported | Rule is fully implemented and produces inferred | | | triples. | +----------------------------------------------------+----------------------------------------------------+ | partial | Rule is implemented but produces a diagnostic | | | instead of new triples (violation detection only). | +----------------------------------------------------+----------------------------------------------------+ | not supported | Rule is not yet implemented. | +----------------------------------------------------+----------------------------------------------------+ RDFS Semantics ============== +--------------------+------------------------+--------------------------------------------------------------+ | Status | Rule | Notes | |====================|========================|==============================================================| | supported | "rdfs11" | "rdfs:subClassOf" transitivity | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "rdfs12" | Container membership: each "rdf:_n" (n > 0) is axiomatised | | | | as "rdf:type rdf:Property", "rdf:type | | | | rdfs:ContainerMembershipProperty", and "rdfs:subPropertyOf | | | | rdfs:member". The last yields "x rdfs:member y" via "prp- | | | | spo1". | +--------------------+------------------------+--------------------------------------------------------------+ | partial | "rdfs-datatype" | Ill-formed typed literal diagnostic ("RDFS.DATATYPE"). | | | | Recognised datatypes: "xsd:string", "xsd:integer", | | | | "xsd:int", "rdf:langString", "rdf:XMLLiteral". Unrecognised | | | | datatypes pass through silently. | +--------------------+------------------------+--------------------------------------------------------------+ | partial | "rdfs-datatype-range" | Diagnostic ("RDFS.DATATYPE_RANGE") when an object literal’s | | | | datatype is not in the value space declared by the | | | | predicate’s "rdfs:range". | +--------------------+------------------------+--------------------------------------------------------------+ Equality Semantics ================== +--------------------+------------------------+--------------------------------------------------------------+ | Status | Rule | Notes | |====================|========================|==============================================================| | not supported | "eq-ref" | Very inefficient — causes runaway flux; not implemented | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "eq-sym" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "eq-trans" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "eq-rep-s" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "eq-rep-p" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "eq-rep-o" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "eq-diff1" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "eq-diff2" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "eq-diff3" | | +--------------------+------------------------+--------------------------------------------------------------+ Property Axiom Semantics ======================== +--------------------+------------------------+--------------------------------------------------------------+ | Status | Rule | Notes | |====================|========================|==============================================================| | not supported | "prp-ap" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-dom" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-rng" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-fp" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-ifp" | | +--------------------+------------------------+--------------------------------------------------------------+ | partial | "prp-irp" | Violation detection only — emits a diagnostic | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-symp" | | +--------------------+------------------------+--------------------------------------------------------------+ | partial | "prp-asyp" | Violation detection only — emits a diagnostic | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-trp" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-spo1" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-spo2" | "owl:propertyChainAxiom", general n-hop chains. Enabled by | | | | default; pass "--no-default-features" to deliberately | | | | disable it. | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-eqp1" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-eqp2" | | +--------------------+------------------------+--------------------------------------------------------------+ | partial | "prp-pdw" | Violation detection only — emits a diagnostic | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "prp-adp" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-inv1" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "prp-inv2" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "prp-key" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "prp-npa1" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "prp-npa2" | | +--------------------+------------------------+--------------------------------------------------------------+ Class Semantics =============== +--------------------+------------------------+--------------------------------------------------------------+ | Status | Rule | Notes | |====================|========================|==============================================================| | supported | "cls-thing" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cls-nothing1" | | +--------------------+------------------------+--------------------------------------------------------------+ | partial | "cls-nothing2" | Violation detection only — emits a diagnostic | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cls-int1" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cls-int2" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cls-uni" | | +--------------------+------------------------+--------------------------------------------------------------+ | partial | "cls-com" | Violation detection only — emits a diagnostic | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cls-svf1" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cls-svf2" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cls-avf" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cls-hv1" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cls-hv2" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "cls-maxc1" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "cls-maxc2" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "cls-maxqc1" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "cls-maxqc2" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "cls-maxqc3" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "cls-maxqc4" | | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "cls-oo" | | +--------------------+------------------------+--------------------------------------------------------------+ Class Axiom Semantics ===================== +--------------------+------------------------+--------------------------------------------------------------+ | Status | Rule | Notes | |====================|========================|==============================================================| | supported | "cax-sco" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cax-eqc1" | | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "cax-eqc2" | | +--------------------+------------------------+--------------------------------------------------------------+ | partial | "cax-dw" | Violation detection only — emits a diagnostic | +--------------------+------------------------+--------------------------------------------------------------+ | not supported | "cax-adc" | | +--------------------+------------------------+--------------------------------------------------------------+ Schema Vocabulary Semantics =========================== +--------------------+------------------------+--------------------------------------------------------------+ | Status | Rule | Notes | |====================|========================|==============================================================| | supported | "scm-eqc1" | "owl:equivalentClass" → "rdfs:subClassOf" (one direction) | +--------------------+------------------------+--------------------------------------------------------------+ | supported | "scm-eqc2" | "owl:equivalentClass" → "rdfs:subClassOf" (other direction) | +--------------------+------------------------+--------------------------------------------------------------+ Not yet implemented =================== Datatype semantics are not currently implemented. Python API ********** The "reasonable" Python package exposes the Rust reasoner through PyO3 bindings. Pre-built wheels are on PyPI for Python 3.9+ on macOS, Linux, and Windows. pip install reasonable The main class is "PyReasoner". Create one instance per reasoning session; it accumulates base triples and manages incremental state. import reasonable r = reasonable.PyReasoner() PyReasoner ========== "PyReasoner()" Create a new reasoner instance with an empty triple store. "load_file(path: str) -> None" Append all triples from a Turtle or N3 file at *path* to the base graph. Raises "OSError" if the file is missing or cannot be parsed. Call multiple times to load several files. "from_graph(graph_or_iterable) -> None" Append triples from an "rdflib.Graph" or any iterable of "(subject, predicate, object)" 3-tuples. Use "update_graph()" instead when you need retraction support. "update_graph(graph_or_iterable) -> bool" Replace the base triples with the contents of the given graph. Computes a diff against the current base: * If only additions are found, the next "reason()" uses incremental materialisation. * If any removals are detected, the next "reason()" performs a full re-materialisation. Returns "True" when removals were detected. "reason() -> list[tuple[Node, Node, Node]]" Run OWL 2 RL materialisation and return all known triples (base plus inferred) as rdflib nodes. After the first call, subsequent calls are incremental unless removals were detected via "update_graph()". "reason_full() -> list[tuple[Node, Node, Node]]" Force a full re-materialisation from base triples, ignoring any incremental state. Equivalent to "clear()" followed by "reason()". "clear() -> None" Reset all inferred state while keeping base triples. The next "reason()" call will perform a full re-materialisation. "get_base_triples() -> list[tuple[Node, Node, Node]]" Return the current base (non-inferred) triples as rdflib nodes. Useful for debugging. Examples ======== Collect inferred triples into a new graph: import rdflib import reasonable g = rdflib.Graph() g.parse("ontology.ttl") g.parse("data.ttl") r = reasonable.PyReasoner() r.from_graph(g) result = rdflib.Graph() for triple in r.reason(): result.add(triple) print(f"{len(result)} triples after materialisation") Incremental update with retraction: import rdflib import reasonable from rdflib import URIRef, RDF ontology = rdflib.Graph() ontology.parse("ontology.ttl") data = rdflib.Graph() data.add((URIRef("urn:sensor1"), RDF.type, URIRef("urn:TemperatureSensor"))) r = reasonable.PyReasoner() r.from_graph(ontology + data) r.reason() # data changes… data.remove((URIRef("urn:sensor1"), RDF.type, URIRef("urn:TemperatureSensor"))) data.add((URIRef("urn:sensor2"), RDF.type, URIRef("urn:HumiditySensor"))) r.update_graph(ontology + data) triples = r.reason() Note: "from_graph()" *appends* to existing base triples. If you want to replace the base entirely, use "update_graph()" instead. Building from source ==================== cd python uv sync --group dev uv run maturin develop -b pyo3 --release # verify uv run python -c "import reasonable; print(reasonable.__version__)" Run the test suite: cd python uv run pytest -q