Reasoning with N3 · Chapter 9 of 10
Chapter 9
Reference
Every surface, option, vocabulary term and error in one place, followed by what the design leaves out on purpose.
n3, off by default (OpenSpec change add-n3-reasoning, built on add-derived-beliefs). The reasoner is eyeron by Jos De Roo (MIT). Until eyeron is on crates.io the feature is available only when building from the repository.Enabling it
Reasoning lives in the crate tm-n3, behind the facade feature n3. The feature is off by default, does not turn on exec, and combines with every other feature. Without it, neither tm-n3 nor eyeron is in the dependency tree. The core tier can reason: --no-default-features --features n3 builds without the query front ends.
Cargo.toml
[dependencies]
tiramemsu = { git = "https://github.com/Volland/tiramemsu", features = ["n3"] }
eyeron comes from crates.io with its HTTP client left out (default-features = false).
Rust API
tiramemsu, feature n3
use tiramemsu::{ReasonOptions, ReasonScope, Rules, TimeMode, TxN3};
// Read-only reasoning on any view.
impl View<'_> {
pub fn reason(&self, rules: Rules, opts: &ReasonOptions) -> Result<ReasonResult>;
pub fn rule_set_info(&self, rule_set: &Value) -> Result<RuleSetInfo>;
}
// Storing rule sets, inside a transaction (trait TxN3). Returns the eid of R.
fn store_rules(&mut self, rule_set: Value, n3: &str) -> Result<Eid>;
// Writing conclusions as derived beliefs.
impl Db {
pub fn materialize(&self, rule_set: Value, opts: &MaterializeOptions) -> Result<MaterializeReport>;
}
pub enum Rules {
Text(String), // N3 text, parsed for this run
Stored(Value), // the live n3:source of a rule set in the view
}
ReasonOptions
| Option | Default | Meaning |
|---|---|---|
scope | ReasonScope::All | All, Graphs(graphs) for members of named graphs, or Seeds { nodes, hops } for statements within hops of the seeds |
time | TimeMode::Pointwise | Pointwise sweeps valid-time segments; Atemporal ignores valid time. A valid_at view always reasons over one snapshot. |
window | Valid::ALWAYS | the valid-time window of the sweep; conclusions are reported only inside it |
max_segments | 1 024 | stop the sweep after this many segments and report Incomplete { limits: [Segments] } |
proof_n3 | false | also return eyeron’s N3 proof text |
limits | eyeron’s defaults | ReasonLimits for each eyeron run: max_iterations, max_match_steps, max_backward_depth, max_term_bytes |
ReasonResult
| Field | Meaning |
|---|---|
conclusions | conclusions not among the loaded statements, each with s, p, o, its interval and a proof { rule_set, rule, premises }; premises are loaded statements by eid or other conclusions by index |
t | the view’s transaction |
now | the pinned clock: the view’s instant |
loaded, pruned | statements loaded, and whether predicate pruning applied |
segments | number of valid-time segments reasoned over |
status | Complete, or Incomplete { limits } naming each limit reached: Segments, Iterations, MatchSteps, BackwardDepth, BackwardSolutions, TermBytes, TermDepth; builtin failures also make a run incomplete and are listed in errors |
contradiction | the first fired inference fuse: its rule, instance and valid time |
rule_set, rule_set_statement | the stored rule set, if the rules came from one, and the eid of its live n3:source statement |
proof_n3 | eyeron’s proof text, with proof_n3: true |
MaterializeOptions
| Option | Default | Meaning |
|---|---|---|
scope, time, window, max_segments, limits | as for reason | the window defaults to unbounded |
require_complete | false | fail with Incomplete instead of committing a partial run |
dry_run | false | report what would be written, commit nothing |
budget | none | a QueryBudget for loading and the commit |
MaterializeReport carries reasoned_at, the commit’s TxReport, derived, existing, widened, stale_support and status (chapter 7). RuleSetInfo carries monotonicity (Monotonic, NonMonotonic { rule, builtin } or ClockDependent { rule, builtin }), the rule count rules, the body predicates, and whether a body has a variable predicate.
Vocabulary
| Term | Kind | Meaning |
|---|---|---|
n3:source | stored | (rule_set n3:source "…"), the one live statement R of a rule set |
n3:derivedBy | stored label | (D n3:derivedBy rule_set) on a materialized conclusion |
n3:rule | stored label | (D n3:rule i), the index of the rule that produced it |
n3:skolem | stored, derived | the key of an invented node, "<R eid>/<rule>/<label>" |
sys:derivedFrom | stored, engine-owned | a support link from a derived statement to a premise or to R |
tm:content | virtual | e tm:content { s p o }, what statement e says |
tm:validFrom, tm:validTo | virtual | a statement’s bounded valid-time ends, as xsd:dateTime |
tm:run tm:now | virtual | the pinned clock; same value as time:localTime |
tm:run tm:segmentFrom, tm:segmentTo | virtual | the bounded ends of the current sweep segment |
Prefixes: n3: is urn:tiramemsu:n3:, tm: is urn:tiramemsu:tm:, sys: is urn:tiramemsu:sys:. Anonymous nodes, blank nodes and statements appear in N3 as urn:tiramemsu:node:N, urn:tiramemsu:bnode:N and urn:tiramemsu:stmt:N. Virtual triples are generated only when a rule body uses them and are never stored. tm: cannot be written.
Errors
| Error | When |
|---|---|
N3Parse { line, message } | rule text does not parse, in reason or store_rules; store_rules leaves no trace |
UnknownRuleSet | Rules::Stored or materialize names a rule set with no live n3:source in the view |
N3NotMaterializable { rule, builtin } | materialize on a non-monotonic or clock-dependent rule set, before reasoning |
N3Contradiction { rule } | an inference fuse fired during materialize; nothing is written |
Incomplete | materialize with require_complete reached a limit; nothing is written |
reason never fails for a fired fuse or a reached limit. It reports them in contradiction and status.
Python, Node.js and the browser
All three go through the JSON bridge, with a read operation reason, a transaction operation storeRules and a database operation materialize. The bridge takes the same options and returns the same result as Rust:
{"op": "reason", "rules": "{ ?x a <urn:tiramemsu:v:Human> } => { ?x a <urn:tiramemsu:v:Mortal> }."}
The bindings wrap the bridge operations:
Python
MORTALITY = "urn:tiramemsu:v:mortality"
with db.transact() as tx:
tx.store_rules(MORTALITY, open("mortality.n3").read())
result = db.now().reason(rule_set=MORTALITY)
preview = db.materialize(MORTALITY, dry_run=True)
Node.js
const MORTALITY = "urn:tiramemsu:v:mortality";
db.transact((tx) => { tx.storeRules(MORTALITY, rulesText); });
const result = db.now().reason({ ruleSet: MORTALITY });
const preview = db.materialize(MORTALITY, { dryRun: true });
MCP
| Tool | Kind | Does |
|---|---|---|
reason | read | runs rule text or a stored rule set over a view; listed in read-only mode |
store_rules | write | stores or supersedes a rule set; absent with --read-only |
materialize | write | writes conclusions as derived beliefs; absent with --read-only |
An agent that may only read can still ask what follows from memory. Writing rules or conclusions needs a server started without --read-only.
Command line
tm reason <file> (--rules <path> | --rule-set <iri>) [--as-of <tx> | --as-of-instant <t>]
[--valid-at <instant> | --valid-during <from>..<to>] [--graph <iri>]... [--atemporal] [--max-segments <n>] [--proof] [--json]
tm rules put <file> <iri> <path>
tm rules show <file> <iri>
tm materialize <file> <iri> [--valid-during <from>..<to>] [--graph <iri>]...
[--require-complete] [--dry-run]
--as-of takes a transaction number. Instants are epoch milliseconds or RFC 3339 dates and date-times, and either side of a window may be empty. --json prints the bridge result.
Limits and non-goals
- No non-monotonic materialization. Negation as failure, collecting and the clock work in
reasononly.materializerefuses them, because the cascade could not keep their conclusions honest. - One proof per conclusion. eyeron returns one proof per conclusion per segment. A stored conclusion rests on one of its possible supports. It is sound, and a later run restores it if that support goes while another remains.
- No SPARQL or Cypher function. Neither language can call the reasoner. Materialized conclusions are ordinary statements, and
sys:derivedFromandn3:derivedByare ordinary predicates, so every dialect reads them. - Nothing runs on commit. Reasoning never runs automatically after a write, and never inside the writer. Call
materializewhen you want conclusions updated. - Backward rules stay in the run. They see the loaded statements only. There is no backward chaining over the store and no OWL profile.
- No I/O from rules. Rules cannot reach the network or the file system.
- Sweeps cost segments. A run costs about one reasoner pass per segment, capped by
max_segments. - Opt-in. The
n3feature is off by default, and eyeron comes from crates.io.
Credits
Reasoning is done by eyeron, a native Rust N3 reasoner by Jos De Roo, released under the MIT license, from the family of the EYE reasoner. Tiramemsu adds the mapping to statements, the two clocks and grounded materialization around it.