From b13cc1cca2bd62d09140a8a20d9bfd77d6e85855 Mon Sep 17 00:00:00 2001 From: LeonSGP <154585401+LeonSGP43@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:55:02 +0800 Subject: [PATCH] docs(cookbook): add Reasoning module notebook (#990) * docs(cookbook): add Reasoning module notebook Add cookbook/introduction/23_Reasoning.ipynb covering the reasoning module with verified, executable examples: - Reasoner facade: add_fact / add_rule / forward_chain - one-shot infer_facts(facts, rules) - backward_chain goal proving with premises - re-run-safe rule deduplication (#732) - DatalogReasoner: semi-naive fixpoint evaluation + variable queries - ExplanationGenerator: Explanation / ReasoningPath records The reasoning module currently has no cookbook coverage even though it ships reasoning_usage.md in the package. All API calls and outputs were verified against semantica/reasoning/reasoner.py, datalog_reasoner.py, and explanation_generator.py. Signed-off-by: LeonSGP43 * docs(cookbook): correct infer_facts semantics description (appends to instance state, no reset) Signed-off-by: LeonSGP43 * docs(cookbook): execute reasoning notebook in Jupyter (real kernel run, stream outputs, execution counts) Signed-off-by: LeonSGP43 --------- Signed-off-by: LeonSGP43 Signed-off-by: LeonSGP43 Signed-off-by: LeonSGP43 Co-authored-by: LeonSGP43 --- cookbook/introduction/23_Reasoning.ipynb | 383 +++++++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 cookbook/introduction/23_Reasoning.ipynb diff --git a/cookbook/introduction/23_Reasoning.ipynb b/cookbook/introduction/23_Reasoning.ipynb new file mode 100644 index 00000000..b685e46c --- /dev/null +++ b/cookbook/introduction/23_Reasoning.ipynb @@ -0,0 +1,383 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b76a5997", + "metadata": {}, + "source": [ + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/semantica-agi/semantica/blob/main/cookbook/introduction/23_Reasoning.ipynb)\n", + "\n", + "# Reasoning Module — Practical Guide\n", + "\n", + "Semantica's `reasoning` module derives new knowledge from existing facts and knowledge graphs. It ships several strategies behind one facade:\n", + "\n", + "- **`Reasoner`** — unified facade with forward chaining, backward chaining, and one-shot `infer_facts`\n", + "- **`DatalogReasoner`** — semi-naive Datalog fixpoint evaluation with variable queries\n", + "- **`ExplanationGenerator`** — human-readable explanations and reasoning paths for inferred conclusions\n", + "- Plus lower-level engines: `ReteEngine`, `SPARQLReasoner`, `GraphReasoner`, temporal reasoning\n", + "\n", + "This notebook walks through the facade, the Datalog engine, and explanations. All APIs are verified against `semantica/reasoning/`." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "52073af7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-26T18:45:55.427457Z", + "iopub.status.busy": "2026-08-26T18:45:55.427247Z", + "iopub.status.idle": "2026-08-26T18:45:57.266607Z", + "shell.execute_reply": "2026-08-26T18:45:57.264783Z" + } + }, + "outputs": [], + "source": [ + "!pip install -q semantica" + ] + }, + { + "cell_type": "markdown", + "id": "06deb916", + "metadata": {}, + "source": [ + "## 1) Forward chaining with the `Reasoner` facade\n", + "\n", + "Facts are simple `Predicate(args)` strings. Rules use `IF THEN ` with `?x`-style variables. `forward_chain()` derives everything possible and returns a list of `InferenceResult` objects." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "519ca92d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-26T18:45:57.270791Z", + "iopub.status.busy": "2026-08-26T18:45:57.270352Z", + "iopub.status.idle": "2026-08-26T18:45:59.991941Z", + "shell.execute_reply": "2026-08-26T18:45:59.990678Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "

🧠 Semantica - 📊 Current Progress

StatusActionModuleSubmoduleProgressETARateTimeExtracted
Semantica is reasoning🤔 reasoningReasoner100.0%--0.00s-
Semantica is reasoning🤔 reasoningDatalogReasoner100.0%--0.00s-
Semantica is reasoning🤔 reasoningExplanationGenerator100.0%--0.00s-
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "🔄 Semantica is reasoning: Performing forward chaining 🤔 reasoning Reasoner |░░░░░░░░░░░░░░░| 0.0% ETA: - Rate: - Time: 0.00s Extracted: -" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Inferred 2 new facts\n", + " Human(Jane) (rule: Rule 1, confidence: 1.0)\n", + " Human(John) (rule: Rule 1, confidence: 1.0)\n" + ] + } + ], + "source": [ + "from semantica.reasoning import Reasoner\n", + "\n", + "reasoner = Reasoner()\n", + "\n", + "reasoner.add_fact(\"Person(John)\")\n", + "reasoner.add_fact(\"Person(Jane)\")\n", + "reasoner.add_rule(\"IF Person(?x) THEN Human(?x)\")\n", + "\n", + "results = reasoner.forward_chain()\n", + "print(f\"Inferred {len(results)} new facts\")\n", + "for res in results:\n", + " print(f\" {res.conclusion} (rule: {res.rule_used.name}, confidence: {res.confidence})\")" + ] + }, + { + "cell_type": "markdown", + "id": "c1131c45", + "metadata": {}, + "source": [ + "## 2) One-shot inference with `infer_facts`\n", + "\n", + "`infer_facts(facts, rules)` **adds** the given facts and rules to this `Reasoner` instance, runs forward chaining to fixpoint, and returns the derived facts as strings. It does not reset the instance's existing state — create a fresh `Reasoner()` first if you need isolation between runs." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "26249990", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-26T18:45:59.995447Z", + "iopub.status.busy": "2026-08-26T18:45:59.995069Z", + "iopub.status.idle": "2026-08-26T18:46:00.004107Z", + "shell.execute_reply": "2026-08-26T18:46:00.002873Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "['Employee(Jane, Acme)', 'Employee(John, Acme)']" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from semantica.reasoning import Reasoner\n", + "\n", + "derived = Reasoner().infer_facts(\n", + " facts=[\"WorksFor(John, Acme)\", \"WorksFor(Jane, Acme)\"],\n", + " rules=[\"IF WorksFor(?x, ?y) THEN Employee(?x, ?y)\"],\n", + ")\n", + "derived" + ] + }, + { + "cell_type": "markdown", + "id": "d5504a38", + "metadata": {}, + "source": [ + "## 3) Backward chaining: proving a goal\n", + "\n", + "`backward_chain(goal)` works backwards from a conclusion through the rules. It returns the `InferenceResult` that proves the goal, or `None`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c4ef85dd", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-26T18:46:00.007740Z", + "iopub.status.busy": "2026-08-26T18:46:00.007346Z", + "iopub.status.idle": "2026-08-26T18:46:00.015561Z", + "shell.execute_reply": "2026-08-26T18:46:00.014145Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Human(John)\n", + "premises: ['Person(John)']\n" + ] + } + ], + "source": [ + "from semantica.reasoning import Reasoner\n", + "\n", + "reasoner = Reasoner()\n", + "reasoner.add_fact(\"Person(John)\")\n", + "reasoner.add_rule(\"IF Person(?x) THEN Human(?x)\")\n", + "\n", + "proof = reasoner.backward_chain(\"Human(John)\")\n", + "print(proof.conclusion if proof else \"not provable\")\n", + "print(\"premises:\", proof.premises if proof else None)" + ] + }, + { + "cell_type": "markdown", + "id": "b245581d", + "metadata": {}, + "source": [ + "## 4) Re-run safety\n", + "\n", + "`add_rule` deduplicates rules with identical conditions and conclusion, so re-executing a setup cell (the common Jupyter re-run) does not duplicate rules — see issue #732." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "fb2aeb39", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-26T18:46:00.019091Z", + "iopub.status.busy": "2026-08-26T18:46:00.018881Z", + "iopub.status.idle": "2026-08-26T18:46:00.024042Z", + "shell.execute_reply": "2026-08-26T18:46:00.022836Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Skipping duplicate rule (same conditions/conclusion as 'rule_1'): IF Person(?x) THEN Human(?x)\n" + ] + }, + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from semantica.reasoning import Reasoner\n", + "\n", + "reasoner = Reasoner()\n", + "reasoner.add_fact(\"Person(John)\")\n", + "\n", + "# Simulate a Jupyter cell re-run: add the same rule twice\n", + "r1 = reasoner.add_rule(\"IF Person(?x) THEN Human(?x)\")\n", + "r2 = reasoner.add_rule(\"IF Person(?x) THEN Human(?x)\")\n", + "\n", + "len(reasoner.rules)" + ] + }, + { + "cell_type": "markdown", + "id": "ba2e5c4a", + "metadata": {}, + "source": [ + "## 5) Datalog reasoning\n", + "\n", + "`DatalogReasoner` uses classic Datalog syntax (`head :- body.`) and semi-naive fixpoint evaluation. Queries return variable bindings as a list of dicts — use uppercase variables to ask *which* facts hold." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9ec5c0c4", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-26T18:46:00.026769Z", + "iopub.status.busy": "2026-08-26T18:46:00.026588Z", + "iopub.status.idle": "2026-08-26T18:46:00.034963Z", + "shell.execute_reply": "2026-08-26T18:46:00.032672Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'X': 'tom', 'Z': 'ann'}]" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from semantica.reasoning import DatalogReasoner\n", + "\n", + "datalog = DatalogReasoner()\n", + "datalog.add_fact(\"parent(tom, mary)\")\n", + "datalog.add_fact(\"parent(mary, ann)\")\n", + "datalog.add_rule(\"grandparent(X, Z) :- parent(X, Y), parent(Y, Z)\")\n", + "\n", + "datalog.derive_all()\n", + "datalog.query(\"grandparent(X, Z)\")" + ] + }, + { + "cell_type": "markdown", + "id": "d4f0689b", + "metadata": {}, + "source": [ + "## 6) Explanations for inferred conclusions\n", + "\n", + "`ExplanationGenerator` turns `InferenceResult` objects into structured `Explanation` and `ReasoningPath` records, so agents can show *why* they believe a derived fact." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "19dcd3a7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-26T18:46:00.038649Z", + "iopub.status.busy": "2026-08-26T18:46:00.038396Z", + "iopub.status.idle": "2026-08-26T18:46:00.059188Z", + "shell.execute_reply": "2026-08-26T18:46:00.057805Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "('Explanation', 'ReasoningPath')" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from semantica.reasoning import Reasoner, ExplanationGenerator\n", + "\n", + "reasoner = Reasoner()\n", + "reasoner.add_fact(\"Person(John)\")\n", + "reasoner.add_rule(\"IF Person(?x) THEN Human(?x)\")\n", + "results = reasoner.forward_chain()\n", + "\n", + "gen = ExplanationGenerator()\n", + "explanation = gen.generate_explanation(results[0])\n", + "path = gen.show_reasoning_path(results[0])\n", + "\n", + "type(explanation).__name__, type(path).__name__" + ] + }, + { + "cell_type": "markdown", + "id": "fb882ee4", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "| Task | API |\n", + "|---|---|\n", + "| Derive all new facts | `Reasoner.forward_chain()` |\n", + "| One-shot inference | `Reasoner.infer_facts(facts, rules)` |\n", + "| Prove a goal | `Reasoner.backward_chain(goal)` |\n", + "| Datalog fixpoint | `DatalogReasoner.derive_all()` + `query(\"p(X, Y)\")` |\n", + "| Explain a conclusion | `ExplanationGenerator.generate_explanation(result)` |\n", + "\n", + "See also `semantica/reasoning/reasoning_usage.md` and the module docstrings for `ReteEngine`, `SPARQLReasoner`, and temporal reasoning." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}