# How to Perform SPARQL Reasoning Across RDF Triple Stores with Semantica

> Learn how to perform SPARQL reasoning across RDF triple stores using Semantica. Discover how its TripletStore facade and SPARQLReasoner derive implicit knowledge from your data.

- Repository: [Semantica /semantica](https://github.com/semantica-agi/semantica)
- Tags: how-to-guide
- Published: 2026-09-10

---

**Semantica enables SPARQL reasoning across heterogeneous RDF triple stores by combining a unified `TripletStore` façade with a `SPARQLReasoner` that expands queries using inference rules and post-processes results to derive implicit knowledge.**

Semantica is a modular Python framework designed for semantic reasoning over distributed knowledge graphs. It abstracts backend-specific implementations—such as Blazegraph, Apache Jena, RDF4J, Anzo, and Oxigraph—behind a common interface, allowing you to run inference-augmented SPARQL queries without modifying application logic when switching stores.

## Understanding the Core Architecture

The framework centers on three primary components defined in the `semantica-agi/semantica` repository:

- **`TripletStore`** ([`semantica/triplet_store/triplet_store.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/triplet_store/triplet_store.py)): Validates the backend against `SUPPORTED_BACKENDS`, initializes the concrete store connection, and instantiates a `QueryEngine`.
- **`QueryEngine`** ([`semantica/triplet_store/query_engine.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/triplet_store/query_engine.py)): Validates SPARQL syntax, manages optional caching, applies query optimizations, and delegates execution to the backend’s native endpoint.
- **`SPARQLReasoner`** ([`semantica/reasoning/sparql_reasoner.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/reasoning/sparql_reasoner.py)): Orchestrates the reasoning workflow by expanding queries with inference rules before execution and enriching result sets afterward.

This separation allows reasoning logic to remain agnostic of whether the underlying store is a remote Jena Fuseki endpoint or an embedded Oxigraph database.

## Initializing the TripletStore

Begin by selecting your RDF backend. The `TripletStore` constructor validates the selection and creates a backend-specific store object (e.g., `JenaStore`) along with a `QueryEngine` instance stored in `self.query_engine`.

```python
from semantica.triplet_store import TripletStore

# Connect to a remote Jena endpoint

store = TripletStore(
    backend="jena",
    endpoint="http://localhost:3030/ds",
)

# Or use an in-memory Oxigraph store (no external endpoint required)

local_store = TripletStore(backend="oxigraph")

```

The backend identifier must exist in `SUPPORTED_BACKENDS`. Upon initialization, `TripletStore` also configures namespaces and prepares bulk-loading utilities.

## Loading Data into the Store

Before reasoning, populate the store with RDF data. The `store()` method converts input dictionaries or objects into internal `Triplet` representations and uses the backend’s bulk loader.

```python
store.store(knowledge_graph=my_kg, ontology=my_ontology)

```

This extracts standard RDF namespaces and inserts triples via the backend-specific implementation found in modules like [`semantica/triplet_store/jena_store.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/triplet_store/jena_store.py).

## Configuring the SPARQLReasoner

 Instantiate the `SPARQLReasoner` with your initialized store and enable inference processing. The reasoner creates an internal `Reasoner` instance (a generic rule engine) for managing rule sets.

```python
from semantica.reasoning import SPARQLReasoner

reasoner = SPARQLReasoner(
    triplet_store=store, 
    enable_inference=True
)

```

When `enable_inference` is active, the reasoner will expand queries and post-process results using the rule engine defined in [`semantica/reasoning/reasoner.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/reasoning/reasoner.py).

## Expanding Queries with Inference Rules

The `expand_query()` method parses your SPARQL, retrieves active inference rules from the internal `Reasoner`, converts each rule to a SPARQL pattern via `_rule_to_sparql`, and appends these patterns as comment-marked blocks to the original query.

```python
base_query = """
SELECT ?person WHERE {
    ?person a <http://example.org/Person> .
}
"""

expanded_query = reasoner.expand_query(base_query)

# Output includes original query plus "# Inference: ..." blocks

```

This expansion ensures that the triple store evaluates not only explicit patterns but also rule-derived patterns during execution.

## Executing Queries Through the QueryEngine

Currently, `SPARQLReasoner.execute_query` is a stub raising `NotImplementedError` until the triplet-store execution path is finalized. Execute the expanded query directly through the store’s `QueryEngine`, which handles validation, caching, and optimization.

```python
raw_result = store.query_engine.execute_query(
    expanded_query,
    store_backend=store._store_backend,  # The concrete backend instance

    graph=None,                            # Optional named graph

)

```

The `QueryEngine` performs the following steps:
1. Validates SPARQL syntax
2. Checks `self.query_cache` for existing results (if enabled)
3. Applies `optimize_query()` transformations
4. Delegates to `store_backend.execute_sparql()`

It returns a `QueryResult` dataclass containing `bindings`, `variables`, execution `metadata`, and for `CONSTRUCT` queries, the raw triples.

## Post-Processing Results for Inference

Wrap the raw `QueryResult` in a `SPARQLQueryResult` and pass it to `infer_results()` to apply rule-based deductions after retrieval. This method iterates enabled rules, applies them via `_apply_rule_to_results`, generates new bindings through `_generate_binding_from_conclusion`, and deduplicates results using `_deduplicate_bindings`.

```python
from semantica.reasoning.sparql_reasoner import SPARQLQueryResult

# Wrap the raw engine output

sparql_result = SPARQLQueryResult(
    bindings=raw_result.bindings,
    variables=raw_result.variables,
    metadata=raw_result.metadata,
)

# Apply inference rules to derive additional results

final_result = reasoner.infer_results(sparql_result)

print(f"Original count: {final_result.metadata['original_count']}")
print(f"Inferred count: {final_result.metadata['inferred_count']}")

```

The returned object includes metadata tracking the number of inferred versus original bindings.

## Adding Custom Inference Rules

Define domain-specific logic using the rule syntax `?x predicate object => ?x derived_predicate derived_object`. The `add_inference_rule()` method delegates to the underlying `Reasoner` which parses and stores the rule for future expansion and inference cycles.

```python

# Rule: Every Person is also a Mortal

rule_text = "?x is_a Person => ?x is_a Mortal"
new_rule = reasoner.add_inference_rule(rule_text)

print(f"Registered rule: {new_rule.name}")

```

Once added, this rule automatically participates in query expansion (fetching Persons when querying for Mortals) and result post-processing.

## Complete Workflow Example

The following example demonstrates an end-to-end pipeline using an in-memory Oxigraph backend:

```python
from semantica.triplet_store import TripletStore
from semantica.reasoning import SPARQLReasoner
from semantica.reasoning.sparql_reasoner import SPARQLQueryResult

# 1️⃣ Initialize store

store = TripletStore(backend="oxigraph")

# 2️⃣ Load data

store.store(knowledge_graph=my_kg, ontology=my_ontology)

# 3️⃣ Create reasoner with inference enabled

reasoner = SPARQLReasoner(triplet_store=store, enable_inference=True)

# 4️⃣ Define query

query = """
SELECT ?x WHERE {
    ?x a <http://example.org/Mortal> .
}
"""

# 5️⃣ Expand with rules (includes Persons if the rule above was added)

expanded = reasoner.expand_query(query)

# 6️⃣ Execute via QueryEngine

raw = store.query_engine.execute_query(
    expanded,
    store_backend=store._store_backend,
)

# 7️⃣ Wrap and infer

wrapped = SPARQLQueryResult(
    bindings=raw.bindings,
    variables=raw.variables,
    metadata=raw.metadata,
)
final = reasoner.infer_results(wrapped)

print("Final bindings:", final.bindings)

```

## Summary

- **Semantica** unifies access to Blazegraph, Jena, RDF4J, Anzo, and Oxigraph through the `TripletStore` façade in [`semantica/triplet_store/triplet_store.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/triplet_store/triplet_store.py).
- **Query execution** flows through `QueryEngine.execute_query()`, which validates, caches, and optimizes SPARQL before delegating to backend-specific endpoints.
- **Reasoning** occurs in two phases: `SPARQLReasoner.expand_query()` modifies queries to include rule patterns, while `infer_results()` derives new bindings from existing data post-execution.
- **Custom rules** are registered via `add_inference_rule()` and parsed by the `Reasoner` class in [`semantica/reasoning/reasoner.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/reasoning/reasoner.py), enabling domain-specific inference without modifying core code.

## Frequently Asked Questions

### Which RDF triple stores are supported by Semantica?

Semantica supports Blazegraph, Apache Jena, RDF4J (Eclipse rdf4j), Anzo, and Oxigraph. The `TripletStore` constructor validates your selection against an internal `SUPPORTED_BACKENDS` registry and instantiates the appropriate concrete store class (e.g., `JenaStore` or `OxigraphStore`) located in `semantica/triplet_store/`.

### How does query expansion work in the SPARQLReasoner?

When you call `expand_query()`, the reasoner parses your SPARQL string, retrieves all active rules from its internal `Reasoner` instance, converts each rule to a SPARQL graph pattern using `_rule_to_sparql`, and appends these patterns as inference blocks to your query. This ensures the triple store evaluates both explicit and implicit patterns during execution.

### Is query caching available for reasoning workflows?

Yes, caching is implemented in the `QueryEngine` class ([`semantica/triplet_store/query_engine.py`](https://github.com/semantica-agi/semantica/blob/main/semantica/triplet_store/query_engine.py)) via `self.query_cache`. While `SPARQLReasoner.execute_query()` is currently a stub awaiting full implementation, executing queries directly through `store.query_engine.execute_query()` leverages this cache automatically, checking for stored results before running expensive SPARQL operations.

### What syntax is used for custom inference rules?

Rules follow the pattern `?variable predicate object => ?variable derived_predicate derived_object`. For example, `?x is_a Person => ?x is_a Mortal` creates a subclass inference. The `add_inference_rule()` method passes this string to the underlying `Reasoner`, which parses and stores it for use in both query expansion and result post-processing phases.