psychologyReasoning — OWL 2 RL and RDFS
pgrdf.materialize(graph_id BIGINT, profile TEXT DEFAULT 'owl-rl') → JSONB computes everything that follows from one graph's triples and stores the result in the same graph, flagged as inferred. The profile argument picks the rule set:
'owl-rl'(default): OWL 2 RL, run by thereasonablereasoner.'rdfs': the RDFS closures only (subclass, subproperty, domain, range).
Re-running replaces the previous inferred triples. Asserted triples are never changed. After the write, materialize refreshes planner statistics (setting pgrdf.auto_analyze, on by default), so queries over the enlarged graph stay fast.
At a glance
SELECT pgrdf.add_graph('urn:example:people');
SELECT pgrdf.parse_turtle('
@prefix ex: <http://example.com/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
ex:Engineer rdfs:subClassOf ex:Person .
ex:Person rdfs:subClassOf ex:Agent .
ex:alice a ex:Engineer .
', pgrdf.graph_id('urn:example:people'));
SELECT pgrdf.materialize(pgrdf.graph_id('urn:example:people'));
-- {"diff_ms": 0.002, "load_ms": 0.386, "profile": "owl-rl", "write_ms": 0.276,
-- "reason_ms": 0.096, "analyze_ms": 0.202, "elapsed_ms": 1.075,
-- "base_triples": 3, "auto_analyzed": true, "reasoner_errors": [],
-- "inferred_triples_written": 10, "previous_inferred_dropped": 0}ex:alice is now also an ex:Person and an ex:Agent, and SPARQL returns those types like any other triple. The worked example walks through it step by step.
The return value
| Field | Meaning |
|---|---|
profile | The rule set that ran. |
base_triples | Asserted triples read as input. |
inferred_triples_written | Entailed triples, not already asserted, written by this call. |
previous_inferred_dropped | Inferred triples from the previous run that this call replaced. |
reasoner_errors | Problems reported by the reasoner; an empty array when there are none. |
auto_analyzed | Whether planner statistics were refreshed after the write. |
elapsed_ms | Total time for the call. |
load_ms, reason_ms, diff_ms, write_ms, analyze_ms | Time spent reading the graph, reasoning, separating new triples from asserted ones, writing, and refreshing statistics. |
Timings vary with hardware; the counts don't.
Where inferred triples show up
| Function | Inferred triples |
|---|---|
sparql, construct, describe | Included. Queries see asserted and inferred triples as one set. |
validate | Included. Shapes are checked against the entailed closure. |
graph_inventory() | Counted separately, in the inferred column. count_quads returns asserted plus inferred. |
export_graph, graph_digest, structural_digest | Excluded. They cover asserted triples only. |
copy_graph | Carried to the destination. The copy's materialization reads unknown. |
drop_graph(g, cascade => false) | Refuses with SQLSTATE 2BP01 while the graph holds inferred triples. The default, cascade => true, drops them with the graph. |
Is the materialization current?
Inferred triples are a snapshot. They don't change when you load or delete asserted triples; they change when you run materialize again. pgrdf.graph_inventory() tells you, per graph, whether that snapshot still matches:
SELECT iri, asserted, inferred, materialization
FROM pgrdf.graph_inventory()
WHERE iri LIKE 'urn:example:%';
-- iri | asserted | inferred | materialization
-- ------------------------+----------+----------+-----------------
-- urn:example:people | 4 | 13 | current
-- urn:example:people-copy | 4 | 13 | unknownmaterialization | Meaning |
|---|---|
never | materialize has not run on this graph. |
current | The asserted triple count is the same as at the last run. |
stale | Asserted triples were added or removed since the last run. |
unknown | The graph holds inferred triples with no run recorded for them, for example after copy_graph. |
To refresh a stale or unknown graph, run materialize on it again:
SELECT pgrdf.parse_turtle('@prefix ex: <http://example.com/> . ex:bob a ex:Engineer .',
pgrdf.graph_id('urn:example:people'));
SELECT materialization FROM pgrdf.graph_inventory() WHERE iri = 'urn:example:people';
-- stale
SELECT pgrdf.materialize(pgrdf.graph_id('urn:example:people'));
SELECT materialization FROM pgrdf.graph_inventory() WHERE iri = 'urn:example:people';
-- currentFreshness follows the asserted count
The check compares the number of asserted triples. An edit that leaves the count unchanged, such as deleting one triple and inserting another in the same transaction, still reads current. If you change a graph that way, run materialize anyway. It is always safe to re-run.
Refusals
| Situation | SQLSTATE | Message |
|---|---|---|
| Unknown profile | 22023 | materialize: unknown profile "bogus" (supported: 'owl-rl', 'rdfs') |
| Graph is locked | 55P03 | pgrdf: graph 1 is locked (release review): materialize refused. Unlock with pgrdf.unlock_graph(1, '<reason>'). |
See Errors and diagnostics for handling SQLSTATEs in your driver.
Reason over a right-sized graph
Loading is parallel and scales to billions of triples through the staged loader. Reasoning is single-threaded per graph, so run materialize on a graph sized for your hardware and batch window. To reason over part of a large graph, copy that part out with carve_graph and materialize the slice. Scale of reasoning has measured numbers.
Topics
- info Mental model: what materialization stores and how queries see it.
- description Worked example: a subclass chain you can run in psql.
- psychology OWL 2 RL rule set: what the reasoner entails.
- settings Idempotence and scheduling: re-running safely from a scheduled job.
- swap_horiz Reasoning profiles: choosing between
'owl-rl'and'rdfs'. - query_stats Scale of reasoning: measured LUBM numbers and how to size the graph you reason over.
Learn more
- school OWL 2 Profiles: OWL 2 RL, the W3C profile.
- school OWL 2 RL/RDF rules, the forward-chaining rules.
- code
reasonable, the OWL 2 RL reasoner pgRDF uses.