Skip to main content

A Prolog-style logic programming DSL embedded in Python

Project description

clausal

A Prolog-style logic programming DSL embedded in Python. Write relational logic programs in .clausal source files that load via Python's standard import system, with full constraint solving, tabling, DCGs, and a rich standard library.

Features

  • .clausal source files — import logic modules with Python's standard import system
  • Prolog-style predicates — Horn clauses, unification, backtracking search
  • Constraint solving — CLP(FD) for integers, CLP(B) for booleans, Dif/2 disequality
  • SLG tabling — memoised subgoal calls for termination on cyclic structures
  • DCGs — Definite Clause Grammars with >> syntax and Phrase/2,3
  • Well-Founded Semantics — negation-as-failure for tabled predicates
  • Module system-import_from/2, -import_module/1, qualified calls
  • Meta-predicatesFindAll/3, BagOf/3, SetOf/3, ForAll/2, Call/N
  • Higher-orderMapList/2,3, FoldLeft/4, Filter/3, Exclude/3
  • Python interop++expr escape, lambda goal closures, f-string support in terms
  • SciPy integration — wrappers for scipy.special, scipy.linalg, scipy.optimize, scipy.interpolate, scipy.signal
  • RegexMatch/2,3, Search/2,3, Replace/4, Split/3 with auto-binding goal expansion
  • Term expansion — macro system for source-level term rewriting
  • C extensions — fast logic variables, trail-based backtracking, trampoline (no WAM)

Installation

pip install clausal

Requires Python ≥ 3.13 and a C compiler (used automatically by pip when building from source).

Quick start

.clausal source files

Facts use a trailing comma. Rules use <- with a parenthesised, comma-separated body. Predicate names are CamelCase; variables are ALLCAPS.

# family.clausal
Parent(tom, bob),
Parent(tom, liz),
Parent(bob, ann),
Parent(bob, pat),

Grandparent(X, Z) <- (Parent(X, Y), Parent(Y, Z))
import family   # .clausal files load via the import hook

from clausal import call, deref, Var

GRANDCHILD = Var()
results = [deref(GRANDCHILD) for _ in call("Grandparent", "tom", GRANDCHILD, module=family)]
# → ['ann', 'pat']

Arithmetic

# fib.clausal
Fib(0, 0),
Fib(1, 1),
Fib(N, RESULT) <- (
    N > 1,
    N1 := N - 1,
    N2 := N - 2,
    Fib(N1, A),
    Fib(N2, B),
    RESULT := A + B
)

Tabling (memoisation for cyclic graphs)

-table(Path/2)

Edge(1, 2),
Edge(2, 3),
Edge(3, 1),

Path(X, Y) <- Edge(X, Y)
Path(X, Y) <- (Edge(X, Z), Path(Z, Y))

DCGs

Sentence >> (NounPhrase, VerbPhrase)
NounPhrase >> (["the", "dog"] or ["the", "cat"])
VerbPhrase >> (["runs"] or ["barks"])
from clausal import once
result = once(call("Phrase", "Sentence", ["the", "cat", "runs"], module=grammar))

CLP(FD) — constraint logic programming over integers

Sendmoney(S, E, N, D, M, O, R, Y) <- (
    InDomain([S, E, N, D, M, O, R, Y], 0, 9),
    AllDifferent([S, E, N, D, M, O, R, Y]),
    S != 0,
    M != 0,
    Label([S, E, N, D, M, O, R, Y]),
    SEND  := S * 1000 + E * 100 + N * 10 + D,
    MORE  := M * 1000 + O * 100 + R * 10 + E,
    MONEY := M * 10000 + O * 1000 + N * 100 + E * 10 + Y,
    SEND + MORE == MONEY
)

Meta-predicates

Squares(Ns, Squares) <- (
    FindAll(
        Sq,
        (In(X, Ns), Sq := X * X),
        Squares
    )
)

Testing

.clausal files can include inline tests as Test/1 clauses:

Test("fib(5) = 5") <- Fib(5, 5)
Test("tom's grandchildren") <- (
    Grandparent(tom, ann),
    Grandparent(tom, pat)
)

Standalone runner:

python -m clausal.testing clausal/examples/           # all .clausal files
python -m clausal.testing clausal/examples/hanoi.clausal  # single file
python -m clausal.testing -v clausal/examples/        # verbose

Via pytest (.clausal tests collected automatically):

python -m pytest tests/ clausal/examples/ -q

Logic variables and backtracking

The C extension clausal.logic.variables provides Prolog-style logic variables and trail-based backtracking without a Warren Abstract Machine.

from clausal.logic.variables import Var, Trail, unify, is_var

trail = Trail()
X = Var()
Y = Var()

unify(X, 42, trail)
assert X.value == 42

mark = trail.mark()
unify(Y, "temporary", trail)
trail.undo(mark)
assert is_var(Y)   # Y is unbound again

Constraints

from clausal.logic.variables import Var, Trail, unify
from clausal.logic.constraints import dif

trail = Trail()
X, Y = Var(), Var()

dif(X, Y, trail)      # post: X ≠ Y
unify(X, 1, trail)    # ok — still satisfiable
unify(Y, 2, trail)    # ok — satisfied (1 ≠ 2)

Requirements

  • Python ≥ 3.13
  • greenlet
  • pyyaml ≥ 6.0
  • C compiler (for building from source)

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clausal-0.3.1.tar.gz (575.1 kB view details)

Uploaded Source

File details

Details for the file clausal-0.3.1.tar.gz.

File metadata

  • Download URL: clausal-0.3.1.tar.gz
  • Upload date:
  • Size: 575.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for clausal-0.3.1.tar.gz
Algorithm Hash digest
SHA256 b6a506c2341c062f9e3b6ff151932570e35af86facb100d273f11249e375baee
MD5 9e2b0743efb0c4a284043889787705aa
BLAKE2b-256 aaf3317f384940f9daa71bb636eef527f1d95dcc23e3dfb89ce46f80397d004b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page