
Prolog in Python for Neurosymbolic AI¶
Neural networks are good at perception, language and pattern-matching, but are slow, resource-hungry, unreliable and need to be constrained. Symbolic AI is good at rules, reasoning and guarantees but can't deal with the real world. Neurosymbolic AI combines the two, giving you the best of both worlds:
- Capable: neural networks do what hand-written rules can't. They read images, understand and generate language, and learn patterns from data.
- Explainable: conclusions come from rules you can read and a derivation you can trace, not only from weights.
- Reliable: hard constraints such as regulations, safety rules and business policy hold exactly, every time, rather than approximately.
- Data-efficient: knowledge you can state as a rule doesn't have to be learned from examples.
- Correctable: change a rule and the behaviour changes, with no retraining.
Each side already has a leading platform. Python is the de facto home of neural networks: PyTorch, JAX and the rest of the deep-learning ecosystem are built for it. Prolog is the leading language of symbolic AI. It has more than 50 years of maturity behind it, and it is still chosen for demanding systems, IBM Watson's question analysis among them, for its robustness and expressive power. Neurosymbolic AI needs the two working together.
Clausal Prolog is a Prolog implemented in Python that aims for ISO Prolog conformity. It is not a wrapper around an external engine: it runs in the Python process, on the same objects and the same garbage collector, so Prolog and Python call each other freely. A neural model can call a logic program, and that logic can call back into the model, with no serialisation in between.
Start here¶
-
Relations, queries and unification, from first principles. No Prolog experience needed.
-
The one idea to absorb first: predicates are relations, not functions.
-
From functions and loops to relations and search.
-
What's the same as ISO Prolog, what's different, and where Python comes in.
-
Why LLMs should generate logic programs, and how to do it well.
-
The business case: explainability, reliability, rules as code.
The rest of this page is a quick tour. Command line and Examples are good next stops after the tutorial.
Running Prolog¶
Install the package from PyPI. It also installs the clausal command:
Write ordinary Prolog in a .clausal file. Tests live next to the code:
% fibonacci.clausal
:- module(fibonacci, [fib/2]).
:- use_module(library(clpz)).
:- table(fib/2).
fib(0, 0).
fib(1, 1).
fib(N, F) :-
N #> 1,
N1 #= N - 1, N2 #= N - 2,
fib(N1, F1), fib(N2, F2),
F #= F1 + F2.
test("fib(10) = 55") :- fib(10, 55).
:- end_module(fibonacci).
Run it with the clausal command. -g runs a goal and prints its answers,
the way a Prolog toplevel does; --test runs the module's tests:
$ clausal fibonacci.clausal -g "fib(10, F)"
F = 55.
$ clausal --test fibonacci.clausal
1 tests: 1 passed, 0 failed [PASSED]
A program that defines main/0 runs it when given just the file:
clausal program.clausal. See Command line.
Existing ISO Prolog lives in .pl files; see Importing Prolog.
Running from Python¶
Import the module like any Python module and ask it questions. In a .seam
file, a goal after -- is a query, and its variables become Python
variables:
Answers are plain Python values: an atom is a str, a compound term is a
tuple such as ('point', 1, 2). See Python Integration.
Three source surfaces¶
| Extension | Surface | Use it for |
|---|---|---|
.clausal |
Clausal Prolog: ISO syntax, cut-free | Your logic programs |
.pl |
ISO Prolog (experimental) | Existing Prolog code |
.seam |
The seam: Python syntax | The boundary with Python: ++expr, hosted Python, adapters |
Many language pages in these docs still show their examples in seam syntax. Each one says so, and the semantics carry over to Clausal Prolog. Clausal Prolog gives the spelling differences and the rules the surface enforces.
Why Clausal Prolog?¶
- Heading for ISO Prolog — ISO syntax, ISO builtin names and ISO error terms; where ISO is silent, Clausal Prolog follows Scryer Prolog. See Operators and Public API.
- Pure by design — no cut and no committed choice:
dif/2, reified if-then-else, constraints and tabling keep programs monotonic. See Purity. - Deep integration — terms are Python tuples and atoms are Python strings, backtracking uses Python generators, and Python libraries are reached through
library(...)facades and.seammodules. - Asyncio, not a home-grown scheduler — queries run on Python's own mature
asyncioevent loop rather than a scheduler of our own. A proof can wait on a model, a database, a person or an event stream while other queries run, and timeouts, cancellation and racing come straight fromasyncio. Backtracking still works across waits (experimental). - Full-featured — tabling, CLP(ℤ), DCGs, EDCGs, modules, term expansion, goal expansion, reified if-then-else.
- Fast — C extension for unification/trails, first-argument indexing, groundness-keyed dispatch, tail recursion optimization, bytecode caching.
Interactive example — Sudoku in IPython¶
The IPython integration uses seam syntax, since it runs inside Python.
Start IPython with the integration enabled:
Then solve a Sudoku puzzle interactively:
In [1]: from clausal.examples.sudoku import *
In [2]: *(ROWS is [
...: [1, _, _, 8, _, 4, _, _, _],
...: [_, 2, _, _, _, _, 4, 5, 6],
...: [_, _, 3, 2, _, 5, _, _, _],
...: [_, _, _, 4, _, _, 8, _, 5],
...: [7, 8, 9, _, 5, _, _, _, _],
...: [_, _, _, _, _, 6, 2, _, 3],
...: [8, _, 1, _, _, _, 7, _, _],
...: [_, _, _, 1, 2, 3, _, 8, _],
...: [2, _, 5, _, _, _, _, _, 9],
...: ], solve(ROWS))
Out[2]: ROWS is [
[1, 5, 6, 8, 9, 4, 3, 2, 7],
[9, 2, 8, 7, 3, 1, 4, 5, 6],
[4, 7, 3, 2, 6, 5, 9, 1, 8],
[3, 6, 2, 4, 1, 7, 8, 9, 5],
[7, 8, 9, 3, 5, 2, 6, 4, 1],
[5, 1, 4, 9, 8, 6, 2, 7, 3],
[8, 3, 1, 5, 4, 9, 7, 6, 2],
[6, 9, 7, 1, 2, 3, 5, 8, 4],
[2, 4, 5, 6, 7, 8, 1, 3, 9]
]
# No more solutions.
Uppercase names (ROWS) are automatically allocated as logic variables. The
*(...) form is the IPython query syntax — see IPython / Jupyter REPL
for the full feature set.
What's inside¶
| Section | What you'll find |
|---|---|
| Foundations | |
| Thinking Relationally | The most important idea: predicates as relations, not functions |
| Purity and Monotonicity | Why pure code has better properties and how to write it |
| Start Here | |
| For Python Programmers | Bridge from functions and loops to relations and search |
| For Prolog Programmers | Syntax mapping, what's the same, what's different |
| For AI Agents | Why LLMs should generate logic programs |
| For Decision Makers | The business case: explainability, reliability, rules-as-code |
| Getting Started | |
| Syntax | The trailing-comma convention, escape operators, logic variables, clause syntax |
| Style & Formatting | One goal per line, clause separation, clausal-fmt |
| Clausal Prolog | The .clausal surface: ISO syntax, cut-free, its rules and how it differs from the seam |
| Predicates | How to define predicates |
| Builtins | Complete index of built-in predicates |
| Dicts & Sets | DictTerm, SetTerm, __unify__ protocol |
| Constraints | dif/2, CLP(ℤ) integer constraints, and CLP(ℝ) real-domain constraints |
| CLP(ℝ) | Interval arithmetic, non-linear propagation, and bisection labeling over the reals |
| CLP(Q) | Exact rational constraints via Gaussian elimination and the revised simplex method |
| Tabling | SLG resolution and well-founded semantics |
| Lambdas | Goal closures for higher-order logic programming |
| If-Then-Else | Reified branching (no cut, no committed choice) |
| Import System | Module loading, module directives, qualified calls |
| Importing Prolog | Import .pl files directly — on-the-fly translation and caching (experimental in 1.0) |
| Architecture | Layer stack, execution model, why not a WAM |
| Python Integration | Querying with --goal, the ++() escape, solve(), converters |
| Asyncio | Queries on Python's asyncio loop: waiting on models, data, people and streams mid-search |
| Operators | What each operator means bare (Python) and quoted (Scryer/ISO) |
| Arithmetic | Evaluation, exact rationals, the evaluable functors |
| Public API | What the 1.0 semantic-versioning promise covers |
| Reflection | Reify .seam source as matchable terms — linters and matchers written as logic programs |
| IPython / Jupyter REPL | Interactive queries, *(goals) syntax, solution browsing |
| Standard Library Modules | |
| Physical Units | n(Unit) sugar, dimensional arithmetic, AttVar constraints |
| Currency | Exact-decimal money as units base dimensions; rounding/display; precision checks |
| Regex | Pattern matching, group extraction, auto-binding |
| Symbolic Math | SymPy integration — calculus, algebra, number theory |
| YAML | YAML parsing and generation |
| Date/Time | Date, time, and datetime predicates |
| Logging | Structured logging predicates |
| UUID | UUID generation and inspection |
| Graphs | Graph traversal, pathfinding, connectivity, MST |
| Random | Random number generation, selection, seeding |
| JSON | JSON parsing, generation, DictTerm integration |
| CSV | CSV parsing, generation, DictTerm records |
| OS | Environment variables, working directory, process info, platform |
| Files | File/directory existence, listing, metadata, CRUD, path manipulation |
| Process | Shell commands, subprocess execution, sleep |
| SQLite | SQLite database predicates |
| spaCy NLP | NLP pipeline — tokenisation, NER, POS, similarity |
| scipy.special | Special mathematical functions — gamma, Bessel, elliptic, hypergeometric, orthogonal polynomials |
| scipy.linalg | Linear algebra — solvers, decompositions, matrix functions, factorisations |
| scipy.optimize | Optimisation — minimisation, root finding, curve fitting, linear programming |
| scipy.integrate | Numerical integration — quadrature, ODE solvers, sampled-data methods |
| scipy.interpolate | Interpolation — splines, PCHIP, Akima, regular grids, radial basis functions |
| scipy.stats | statistics — descriptive stats, hypothesis tests, distributions |
| scipy.fft | Discrete Fourier transforms — FFT, inverse FFT, helper functions |
| scipy.ndimage | N-dimensional image processing — filters, morphology, transforms, measurements |
| scipy.spatial | Spatial algorithms — distance functions, KD-tree, ConvexHull, Delaunay, Rotation |
| Crypto | Cryptographic hashing, HMAC signing, PBKDF2 key derivation |
| HTTP & URL | HTTP requests (GET, POST, JSON), URL encoding and parsing |
| TCP | TCP client/server sockets — connect, listen, send, receive |
| Testing | Writing test predicates, running the test suite |
| DCGs | Definite Clause Grammars for parsing |
| Exceptions | throw/catch, structured error terms |
| Coroutining | freeze/2, when/2, setup_call_cleanup/3, call_nth/2, count_all/2 |
| CLP(B) | Boolean constraint programming |
| Z3 SMT Solver | Multi-theory constraints via Z3 — integers, reals, booleans, bitvectors, arrays, strings, optimization, unsat cores |
| Meta-Interpreter Specialization | Partial deduction — specialize MIs to remove interpretation overhead |
| Prolog Translation | Bidirectional seam ↔ Prolog translation |
| Trealla Prolog Embedding | In-process Trealla Prolog engine via ctypes — fast, lightweight, instant startup |
| Scryer Prolog Embedding | In-process Scryer Prolog engine via PyO3 — lazy queries, tabling support |
| Examples | Example programs: Fibonacci, N-Queens, Sudoku, meta-interpreters |
| Scientific Computing | |
| scikit-learn | Machine learning: estimators, pipelines, cross-validation |
| scipy.cluster | Hierarchical clustering, k-means, vector quantisation |
| scipy.constants | CODATA physical constants, SI prefixes |
| scipy.differentiate | Numerical differentiation: Derivative, Jacobian, Hessian |
| scipy.signal | Signal processing: filter design, filtering, spectral analysis |
| scipy.sparse | Sparse matrices and sparse linear algebra |
| Infrastructure | |
| Compiler | Compilation pipeline: head patterns, body goals, trampoline, TRO |
| Jupyter Notebooks | Notebook integration with HTML rendering |
| Free Threading | Free-threaded Python (PEP 703) support, C extension safety |
| Parallel Predicates | Writing thread-safe predicates |
| Parallel Queries | Running parallel queries from Python |