Replace plain markdown in every docs/reference/ file and docs/concepts.md with rich Mintlify JSX components — CardGroup, Steps, Tabs, AccordionGroup, Tip, Warning, Note, and CodeGroup — for a consistent, navigable, production-grade developer experience.
16 KiB
title, description, icon
| title | description | icon |
|---|---|---|
| Reasoning Module | Forward chaining, Rete, deductive, abductive, SPARQL, Datalog, and temporal reasoning with explainable inference paths. | microchip |
semantica.reasoning derives new knowledge from existing facts using logical rules. Every engine produces explainable inference paths — traceable chains of rules and facts, not black-box conclusions.
Why Reasoning?
Knowledge graphs encode what you know explicitly. Reasoning lets you derive what must logically follow — without manually asserting every implication:
- If A
located_inB and Blocated_inC, then Alocated_inC — without storing that triple - If Alice
parent_ofBob and Bobparent_ofCharlie, then Aliceancestor_ofCharlie - If a drug is contraindicated for a condition class, it's also contraindicated for all subclasses — inferred from the ontology hierarchy
- If an employee's CEO tenure ended in 2020, they cannot have signed contracts as CEO in 2021 — caught by temporal reasoning
Reasoning turns sparse explicit knowledge into a dense, coherent, contradiction-free knowledge base.
What You Get
Main facade — IF/THEN forward-chaining with variable substitution and rule templates. Inference over full knowledge graph structure: transitivity, symmetry, inverses. High-performance pattern matching via the Rete algorithm for large rule sets. Query expansion and property chain inference over RDF graphs. Recursive Horn clause rules with guaranteed fixpoint termination (v0.4.0). All 13 Allen interval algebra relations for time-aware inference.<img src="/assets/img/diagrams/reasoning-chain.svg" alt="Forward chaining inference: known facts + IF/THEN rules produce derived facts with a full traceable explanation path" style={{ width: '100%', borderRadius: '12px', margin: '0 0 24px' }} />
Choosing a Reasoning Engine
| Engine | When to Use |
|---|---|
Reasoner |
Simple IF/THEN rules, transitivity/symmetry templates, one-shot inference |
GraphReasoner |
Rules that operate on graph structure (paths, neighborhoods, multi-hop) |
ReteEngine |
Large rule sets (100+), rules fire repeatedly, performance is critical |
SPARQLReasoner |
Already using RDF/Turtle, need property chains, SPARQL ecosystem tools |
DatalogReasoner |
Recursive rules (ancestry, reachability), guaranteed termination required |
TemporalReasoningEngine |
Time-aware facts, interval relationships, historical validity |
Engines
The unified entry point for rule-based forward-chaining inference. Start here for most use cases.```python
from semantica.reasoning import Reasoner, Rule, Fact, RuleType
reasoner = Reasoner()
# Add base facts
reasoner.add_fact(Fact(subject="John", predicate="is_a", obj="Manager"))
reasoner.add_fact(Fact(subject="John", predicate="is_a", obj="Employee"))
# Add an IF/THEN rule
reasoner.add_rule(Rule(
rule_type=RuleType.FORWARD_CHAIN,
conditions=[
{"subject": "?x", "predicate": "is_a", "object": "Manager"}
],
conclusion={"subject": "?x", "predicate": "has_authority", "object": "true"}
))
# Run inference — always call explicitly after adding facts/rules
result = reasoner.infer()
for inference in result.derived_facts:
print(f"{inference.subject} {inference.predicate} {inference.obj}")
print(f" Derived via: {inference.explanation}")
```
### Built-In Rule Templates
No manual rule authoring required for the three most common patterns:
```python
engine = Reasoner()
# Transitive closure: A→B, B→C ⟹ A→C
engine.apply_transitivity("located_in")
# Symmetry: A knows B ⟹ B knows A
engine.apply_symmetry("knows")
# Inverse: A parent_of B ⟹ B child_of A
engine.apply_inverse("parent_of", "child_of")
result = engine.infer()
```
| Template | Parameters | Description |
| -------- | ---------- | ----------- |
| `apply_transitivity(predicate)` | `predicate: str` | Adds A→C rule for all A→B, B→C chains |
| `apply_symmetry(predicate)` | `predicate: str` | Adds B→A rule for every A→B fact |
| `apply_inverse(predicate, inverse)` | `predicate, inverse: str` | Adds inverse direction for every fact |
<Warning>
Always call `reasoner.infer()` after adding facts and rules. Adding them updates internal state but does **not** trigger inference automatically.
</Warning>
```python
from semantica.reasoning import GraphReasoner
graph_reasoner = GraphReasoner(kg)
# Define a transitive ancestor rule
graph_reasoner.add_rule({
"if": [
{"subject": "?a", "predicate": "parent_of", "object": "?b"},
{"subject": "?b", "predicate": "parent_of", "object": "?c"}
],
"then": {"subject": "?a", "predicate": "ancestor_of", "object": "?c"}
})
inferences = graph_reasoner.infer(kg)
for inf in inferences:
print(f"{inf['subject']} {inf['predicate']} {inf['object']}")
```
```python
from semantica.reasoning import ReteEngine
engine = ReteEngine()
engine.load_rules("rules/domain_rules.json")
results = engine.run(kg)
# Inspect the Rete network
root = engine.get_root()
alpha_nodes = engine.get_alpha_nodes() # single-condition filters
beta_nodes = engine.get_beta_nodes() # join nodes
```
Rule file format (JSON):
```json
{
"rules": [
{
"name": "manager_authority",
"conditions": [
{ "subject": "?x", "predicate": "role", "object": "Manager" },
{ "subject": "?x", "predicate": "dept", "object": "?dept" }
],
"action": {
"subject": "?x",
"predicate": "has_authority_over",
"object": "?dept"
},
"priority": 10
}
]
}
```
| Field | Type | Description |
| ----- | ---- | ----------- |
| `name` | `str` | Unique rule identifier — appears in `ExplanationGenerator` output |
| `conditions` | `List[Dict]` | Pattern to match — use `?variable` for wildcards |
| `action` | `Dict` | Fact to derive when all conditions match |
| `priority` | `int` | Higher priority rules fire first |
<Tip>
Use `ReteEngine` when you have more than ~20 rules or when rules can fire repeatedly. `Reasoner` re-evaluates all rules from scratch each cycle; `ReteEngine` caches partial matches and is orders of magnitude faster.
</Tip>
```python
from semantica.reasoning import SPARQLReasoner
reasoner = SPARQLReasoner(graph=rdf_graph)
result = reasoner.query("""
PREFIX ex: <http://example.org/>
SELECT ?person ?company WHERE {
?person ex:founded ?company .
?company ex:located_in ex:SiliconValley .
}
""")
for row in result.bindings:
print(row["person"], row["company"])
# Property chain inference: A knows B, B colleague_of C ⟹ A knows C
reasoner.add_property_chain("knows", ["knows", "colleague_of"])
inferences = reasoner.infer_property_chains()
```
<Note>
Added in **v0.4.0**. Use `DatalogReasoner` whenever your rules can create cycles — it's the only engine with a termination guarantee.
</Note>
```python
from semantica.reasoning import DatalogReasoner, DatalogFact, DatalogRule
datalog = DatalogReasoner()
# Base facts
datalog.add_fact(DatalogFact("parent", ("alice", "bob")))
datalog.add_fact(DatalogFact("parent", ("bob", "charlie")))
# Recursive rules (Horn clauses)
datalog.add_rule(DatalogRule("ancestor(?X, ?Y) :- parent(?X, ?Y)."))
datalog.add_rule(DatalogRule("ancestor(?X, ?Z) :- parent(?X, ?Y), ancestor(?Y, ?Z)."))
# Evaluate to fixpoint
datalog.evaluate()
# Query
results = datalog.query("ancestor(alice, ?Z)")
# → [{"Z": "bob"}, {"Z": "charlie"}]
```
<Warning>
`Reasoner` has **no cycle detection** — rules that create cycles (A derives B, B derives C, C re-derives A) will loop infinitely. Use `DatalogReasoner` whenever recursive rules are involved.
</Warning>
```python
from semantica.reasoning import TemporalReasoningEngine, TemporalInterval, IntervalRelation
engine = TemporalReasoningEngine()
ceo_tenure = TemporalInterval(start="1997-09-16", end="2011-08-24")
board_member = TemporalInterval(start="2000-01-01", end="2012-06-01")
relation = engine.get_relation(ceo_tenure, board_member)
# → IntervalRelation.DURING (ceo_tenure is fully inside board_member)
```
All 13 Allen interval algebra relations:
| Relation | Meaning |
| -------- | ------- |
| `BEFORE` | A ends before B starts |
| `MEETS` | A ends exactly when B starts |
| `OVERLAPS` | A starts before B, ends inside B |
| `DURING` | A is fully inside B |
| `STARTS` | A and B start together, A ends first |
| `FINISHES` | A and B end together, A starts later |
| `EQUALS` | Identical intervals |
| + 6 inverses | `AFTER`, `MET_BY`, `OVERLAPPED_BY`, `CONTAINS`, `STARTED_BY`, `FINISHED_BY` |
ExplanationGenerator
Generate structured step-by-step explanations for any derived conclusion:
from semantica.reasoning import ExplanationGenerator
generator = ExplanationGenerator(reasoner)
explanation = generator.explain(
conclusion={"subject": "John", "predicate": "has_authority", "object": "true"}
)
print(explanation.conclusion)
print(f"Confidence: {explanation.confidence:.2f}")
print(explanation.justification.summary)
for step in explanation.reasoning_path.steps:
indent = " " * step.depth
print(f"{indent}Step {step.depth}: {step.fact}")
print(f"{indent} via rule: '{step.rule_name}'")
print(f"{indent} premises: {step.premises}")
@dataclass
class Explanation:
conclusion: Dict[str, str] # the fact being explained
confidence: float # aggregated rule confidence
reasoning_path: ReasoningPath # full derivation trace
justification: Justification # plain-language summary
@dataclass
class ReasoningPath:
steps: List[ReasoningStep] # ordered derivation steps
@dataclass
class ReasoningStep:
depth: int # 0 = base fact, n = nth inference
fact: Dict[str, str] # the fact derived at this step
rule_name: str # name of the rule that fired
premises: List[Dict] # facts that triggered this rule
confidence: float # confidence at this step
@dataclass
class Justification:
summary: str # one-sentence natural language explanation
evidence: List[str] # list of supporting source facts
Combining Multiple Reasoning Engines
Different engines cover different expressivity levels — compose them for richer inference:
```python from semantica.reasoning import Reasonerengine = Reasoner()
engine.apply_transitivity("located_in")
engine.apply_symmetry("colleague_of")
structural_result = engine.infer()
```
datalog = DatalogReasoner()
for fact in structural_result.derived_facts:
datalog.add_fact(DatalogFact(fact.predicate, (fact.subject, fact.obj)))
datalog.add_rule(DatalogRule("reachable(?X, ?Z) :- located_in(?X, ?Y), reachable(?Y, ?Z)."))
datalog.evaluate()
```
temporal = TemporalReasoningEngine()
active_facts = [
f for f in datalog.query("reachable(?X, ?Z)")
if temporal.is_active(f, at=datetime(2024, 1, 1))
]
```
generator = ExplanationGenerator(engine)
explanation = generator.explain(
{"subject": "london_office", "predicate": "located_in", "object": "UK"}
)
print(explanation.summary)
```