Back to Articles

PyReason: Temporal Logic Reasoning Over Knowledge Graphs with Explainability Built In

[ View on GitHub ]

PyReason: Temporal Logic Reasoning Over Knowledge Graphs with Explainability Built In

Hook

Most AI systems can’t explain why they reached a conclusion. PyReason takes the opposite approach: every inference comes with a complete proof trace showing exactly which rules and facts led to each decision.

Context

Machine learning models excel at pattern recognition but fail spectacularly at transparency. Ask a neural network why it flagged a transaction as fraudulent, and you’ll get probability scores at best—hardly sufficient for regulatory compliance or debugging complex decision chains. Meanwhile, traditional logic programming systems like Prolog offer perfect explainability but struggle with uncertainty, temporal dynamics, and the graph-structured data that dominates modern applications.

PyReason emerged from this gap in the neuro-symbolic AI space. It’s designed for scenarios where you need the transparency of rule-based systems combined with the flexibility to reason about uncertain information over time. Think fraud detection networks that evolve as transactions occur, supply chain risk assessment as relationships change, or multi-agent systems where entity states shift across timesteps. The project brings annotated logic—where facts carry confidence bounds rather than just true/false values—to Python developers working with graph data.

Technical Insight

Define graph & facts

Define rules

Initial state

Compiled rules

Match & fire

Found bindings

Tighten bounds

Check convergence

Timestep results

Property values

& provenance

User Code

Graph Store

Nodes/Edges

Rule Engine

Inference Engine

Rule Matcher

Pattern matching

Bound Updater

Interval logic

Results API

Query & trace

System architecture — auto-generated

PyReason’s architecture centers on a graph-based reasoning engine that propagates logical inferences through timesteps. You start by defining a graph (nodes and edges), attaching initial facts as properties, then specify logical rules that fire when conditions match. The engine applies these rules iteratively, updating the graph state at each timestep until reaching a fixed point or maximum iteration limit.

Here’s a concrete example showing temporal reasoning over a social network to detect influence propagation:

import pyreason as pr

# Initialize PyReason
pr.reset()
pr.reset_rules()

# Define a graph: users and their connections
pr.add_node('alice')
pr.add_node('bob')
pr.add_node('carol')
pr.add_edge('alice', 'bob', 'follows')
pr.add_edge('bob', 'carol', 'follows')

# Set initial facts with annotated bounds [lower, upper]
pr.add_fact(pr.Fact('alice', 'influential', [1.0, 1.0], 0, 0))  # Alice is influential at t=0

# Define a rule: if X follows Y and Y is influential, X becomes influential
# The annotation [0.7, 0.9] represents uncertainty in influence transfer
rule = pr.Rule(
    'influence_propagation',
    pr.Clause('follows(X, Y)'),
    pr.Clause('influential(Y)'),
    pr.Clause('influential(X)', [0.7, 0.9])
)
pr.add_rule(rule)

# Run inference for 5 timesteps
results = pr.reason(timesteps=5)

# Examine results with full provenance
for node in ['alice', 'bob', 'carol']:
    influence = results.get_node_property(node, 'influential')
    print(f"{node}: {influence}")
    trace = results.get_inference_trace(node, 'influential')
    print(f"  Derived from: {trace}")

The key architectural decision here is the use of annotated logic with interval bounds. Unlike traditional Boolean logic, PyReason represents truth values as [lower_bound, upper_bound] ranges between 0 and 1. This allows encoding uncertainty while maintaining logical rigor—bounds can only tighten (never widen) as inference proceeds, ensuring monotonic convergence. When multiple rules conclude the same fact with different bounds, PyReason applies configurable aggregation functions (intersection, union, or custom operators).

Temporal operators distinguish PyReason from static reasoning systems. You can write rules that reference previous timesteps, enabling patterns like “if property P held for the last 3 timesteps, conclude Q.” The engine maintains a complete history of graph states, making it straightforward to query how properties evolved:

# Rule with temporal constraint: sustained behavior over time
temporal_rule = pr.Rule(
    'sustained_influence',
    pr.Clause('influential(X)', time='t-1'),  # Was influential last timestep
    pr.Clause('influential(X)', time='t-2'),  # And the timestep before
    pr.Clause('highly_influential(X)', [0.9, 1.0])
)

Under the hood, PyReason leverages Numba for just-in-time compilation of the inference loops. The reasoning engine is essentially a fix-point computation: apply all applicable rules to update the graph, repeat until no new facts emerge. Numba compiles these hot paths to native code, delivering performance closer to C than pure Python. For Python 3.9+, the system supports parallel execution across CPU cores, partitioning the graph and rule evaluation across workers.

The open-world assumption is subtle but crucial. In closed-world systems (like SQL databases), absence of a fact implies falsehood—if there’s no row saying “Bob is influential,” then Bob definitively isn’t influential. PyReason adopts open-world semantics: unknown facts have bounds [0, 1] representing total uncertainty. This matters enormously for real-world applications where incomplete information is the norm. You can explicitly represent “we don’t know yet” rather than forcing binary decisions on insufficient data.

Explainability comes from the inference trace mechanism. PyReason tracks which rules and facts contributed to each derived conclusion, building a directed acyclic graph of dependencies. You can query this graph to generate human-readable explanations: “Carol is influential [0.7, 0.9] because Bob is influential [0.7, 0.9] (derived from Alice via rule influence_propagation at t=1), and Bob follows Carol (initial fact), therefore rule influence_propagation applied at t=2.” This audit trail is invaluable for debugging rules, satisfying regulatory requirements, or building user-facing explanations.

Gotcha

Python version constraints bite harder than you’d expect. PyReason officially supports only Python 3.7 through 3.10, with multi-core parallelism requiring exactly 3.9 or 3.10. This stems from Numba’s compilation dependencies—newer Python versions often break compatibility until Numba catches up. If your production stack runs Python 3.11+ or you need bleeding-edge language features, you’ll face an awkward choice between upgrading PyReason’s dependencies yourself (non-trivial) or maintaining a separate environment.

Scalability isn’t PyReason’s forte for massive graphs. The inference complexity grows with graph size, number of rules, and timesteps. A graph with 100,000 nodes and 50 rules evaluated over 20 timesteps can strain memory and CPU, especially since the system maintains complete history. The documentation lacks clear performance benchmarks, so capacity planning requires experimentation. For truly large-scale reasoning (millions of nodes), you’ll likely need to partition problems or look toward distributed logic systems. The 332 stars on GitHub also signal a relatively small community—expect to read source code when troubleshooting edge cases rather than finding abundant Stack Overflow answers.

Verdict

Use PyReason if you’re building systems that demand explainable reasoning over graph data with temporal dynamics or uncertainty, particularly in neuro-symbolic AI applications where you’re combining learned models with rule-based logic. It shines for fraud detection networks, supply chain risk modeling, knowledge graph inference, or any domain where you need to answer “why did the system conclude X?” with a complete proof trace. The annotated logic with interval bounds is genuinely differentiated—few tools offer this combination of graph structure, temporal reasoning, and probabilistic logic with explainability. Skip if you need a battle-tested production framework with massive community support, require Python 3.11+ compatibility, or are working with enormous graphs (millions of nodes) where performance becomes critical. Also skip if your reasoning needs are simple enough for traditional rule engines or if you’re purely doing graph queries without logical inference—PyReason’s value proposition centers on that specific intersection of temporal logic, uncertainty, and explainability over graphs.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/llm-engineering/lab-v2-pyreason.svg)](https://starlog.is/api/badge-click/llm-engineering/lab-v2-pyreason)