Ontology reasoning

The ontology layer compiles immutable named-class/object-property axioms to a standalone positive-Datalog program. It uses no database handle and adds no mandatory solver dependency. It does not change asserted labels, relationships, Cypher results or sampler membership. All database interactions are explicit adapters; core and compiler depend only inward and on the standard library.

Optional offline RDF import

Install gestaltdb[rdf] and import from gestaltdb.ontology.adapters.rdf. import_rdf(RDFDocument(text), ontology_id="urn:my-ontology") returns an immutable import report; call its compile() method before reasoning. Turtle, N-Triples and UTF-8 RDF/XML are supported. Named terms retain full IRIs. Class/object-property declarations and the selected axiom forms are accepted, including binary owl:propertyChainAxiom lists, left-side owl:intersectionOf/owl:unionOf collections (with intersection superclasses expanded to subclass pairs) and named class/property forms; ABox statements, restrictions, builtin class/datatype semantics and other unsupported logic fail strictly by default. Right-side unions, unscoped owl:ReflexiveProperty, longer chains and unused collections are rejected or reported incomplete.

No URLs or files are loaded. Resolve owl:imports explicitly with an in-memory imports={iri: RDFDocument(...)} map. Cycles are deduplicated. JSON-LD plugins, XML DTDs/entities and implicit network import resolution are not supported. RDFImportLimits bounds cumulative bytes, triples and documents. These are not an operating-system sandbox. strict=False returns diagnostics and complete=False on unsupported logic; compiling that report additionally requires allow_incomplete=True. Never represent such output as complete OWL/RDFS reasoning. Standard label/comment/version annotations are counted and ignored, not converted into logical facts.

Sampling and Cypher type hierarchies

gestaltdb.ontology.adapters.hierarchy builds the standalone LabelHierarchy and RelationHierarchy values used by opt-in Cypher matching and sampler expansion from an ontology’s direct SubClassOf and SubPropertyOf axioms:

from gestaltdb.ontology import Ontology, SubClassOf, SubPropertyOf
from gestaltdb.ontology.adapters.hierarchy import (
    label_hierarchy_from_ontology,
    relation_hierarchy_from_ontology,
)

ontology = Ontology("urn:example:freight", (
    SubClassOf("Ocean_Freight", "Transport_Edge"),
    SubClassOf("Refrigerated_Freight", "Ocean_Freight"),
    SubPropertyOf("Ocean_Freight", "Transport_Edge"),
))
labels = label_hierarchy_from_ontology(ontology)
relations = relation_hierarchy_from_ontology(ontology)
assert labels.closure("Transport_Edge") == (
    "Transport_Edge", "Ocean_Freight", "Refrigerated_Freight")
assert relations.closure("Transport_Edge") == (
    "Transport_Edge", "Ocean_Freight")

Pass labels to GraphDB.query(..., label_hierarchy=labels) and relations to SamplerSnapshot.expand_relations(...).

Only direct subsumption is flattened; the ontology revision is not modified and no closure is persisted. Multiple superclasses are preserved, duplicate declarations are deduplicated, and cycles or self-parenting fail closed. EquivalentClasses, EquivalentProperties, intersection and union subsumption require a canonicalization policy that a strict hierarchy cannot infer, so the adapter raises ValueError rather than silently dropping those semantics. Resolve them explicitly or build the hierarchy directly.

Standalone example

from gestaltdb.ontology import Domain, Ontology, SubClassOf, TYPE_PREDICATE, compile_ontology
from gestaltdb.reasoning import DatalogEngine, Fact, ReasoningLimits

plan = compile_ontology(Ontology("people", [
    Domain("worksFor", "Researcher"), SubClassOf("Researcher", "Person"),
]))
result = DatalogEngine().evaluate(
    plan.program, [Fact("alice", "worksFor", "acme")],
    limits=ReasoningLimits(), collect_proofs=True,
)
person = Fact("alice", TYPE_PREDICATE, "Person")
assert person in result
explanation = result.explain(person)
assert not explanation.truncated

Use result.match(subject=..., predicate=..., object=...) for immutable result lookups. The compiled origin table maps exact witness rules back to source axioms. The profile supports subclass/subproperty, named equivalence, domain/range, inverse, symmetric and transitive properties, plus binary property chains, left-side class conjunction/disjunction and scoped reflexivity. Cycles terminate under set semantics. It is a selected gestalt-core-v1 fragment, not full RDFS or OWL support. Equality, datatype reasoning, disjointness, existential generation and negation are not supported. Domain/range infer memberships rather than validate missing data.

Read-only temporal claims

This source adapter never writes claims. The separate explicit, point-only publication operation is documented in Explicit point publication.

Install an optional backend for this example (gestaltdb[lmdb]). The existing read view pins one valid-time point and visible commit prefix. The adapter verifies its source, policy, mapping and ontology revision before returning a scoped result.

from tempfile import TemporaryDirectory
from gestaltdb.graphdb import GraphDB
from gestaltdb.ontology import Domain, Ontology, compile_ontology
from gestaltdb.reasoning import ReasoningLimits
from gestaltdb.reasoning.adapters.claims import ClaimMapping, ClaimSelection, PinnedClaimSource

plan = compile_ontology(Ontology("people", [Domain("worksFor", "Person")]))
with TemporaryDirectory() as directory:
    graph = GraphDB.create(directory, backend="lmdb", serializer="json")
    try:
        graph.assert_claim(subject="alice", predicate="worksFor", object="acme",
                           agent="registry", polarity="positive", valid=(0, 100))
        with graph.read_view(valid_time=50) as view:
            with PinnedClaimSource(view, world="default", ontology_revision=plan.revision_id,
                                   selection=ClaimSelection(), mapping=ClaimMapping()) as source:
                scoped = source.evaluate(plan, limits=ReasoningLimits(), collect_proofs=True)
        assert scoped.context.ontology_revision == plan.revision_id
    finally:
        graph.close()

Only selected positive entity claims participate. Explicit negatives do not suppress independent positive support; confidence does not become a probability. Recognized derived claims are excluded unless deliberately opted in. The source does not initialize identity, rebuild indexes or publish results. A point result does not justify an unbounded validity interval. Raw evidence iteration alone is not a context-bound atomic evaluation; use the adapter’s evaluate method. For bounded batched extraction with explicit budgets, capability probing and per-batch verification, see Pinned batched claim extraction.

Limits and deployment

For explicit current-property-graph projection and derived-relation neighbor sampling, see Current-graph ontology adapters. This is separate from temporal claims.

Inference bounds fail closed; proof-node/edge bounds only truncate the witness trace, explicitly. A trace stores one witness, not all independent supports. Counters are not a wall-clock/RSS sandbox for arbitrary Python inputs. Neither the native engine nor a solver swap makes temporal extraction free. No JVM integration is supported. Clingo was faster on larger dense synthetic closures, but does not provide the same work-counter/witness contract in this v1. See Ontology solver benchmarks and v1 decision for measured trade-offs, startup/footprint caveats and reproducible evidence. The development contract is Ontology v1 implementation contract. For additive relational IR and structural plan inspection, see Relational execution contracts (no new execution semantics).