Post Canonical Systems - A Python implementation of Emil Post's formal string rewriting systems
Project description
Post Canonical Systems
A Python implementation of Emil Post's formal string rewriting systems for computation and formal language exploration.
What is this?
Post Canonical Systems are a foundational model of computation developed by mathematician Emil Post in the 1920s. They define formal languages through string rewriting: starting from initial words (axioms) and repeatedly applying production rules to generate new strings. Despite their simplicity, Post systems are Turing-complete and serve as an elegant framework for exploring computability, formal grammars, and proof derivations.
Features
| Feature | Description |
|---|---|
| Pattern Matching | Variable kinds: ANY (empty or more), NON_EMPTY (1+), SINGLE (exactly 1) |
| Multi-Antecedent Rules | Combine multiple words in a single production |
| Derivation Tracking | Full proof traces showing how each word was derived |
| Visualization Exports | DOT/GraphViz, LaTeX, Mermaid diagrams, ASCII trees |
| Interactive CLI | REPL interface via the pcs command |
| JSON Serialization | Save and load system definitions |
| SystemBuilder DSL | Ergonomic fluent API for system construction |
| Preset Systems | MU Puzzle, Binary Doubler, Palindrome Generator |
Installation
pip install post-canonical
Or with uv:
uv add post-canonical
Quick Start
from post_canonical import (
PostCanonicalSystem,
Alphabet,
Variable,
Pattern,
ProductionRule,
)
# Define alphabet and variable
alphabet = Alphabet("ab")
x = Variable.any("x")
# Create rules: x -> xa and x -> xb (append a or b)
rules = frozenset({
ProductionRule(
antecedents=[Pattern([x])],
consequent=Pattern([x, "a"]),
name="append_a",
),
ProductionRule(
antecedents=[Pattern([x])],
consequent=Pattern([x, "b"]),
name="append_b",
),
})
# Build the system
system = PostCanonicalSystem(
alphabet=alphabet,
axioms=frozenset({"a", "b"}),
rules=rules,
variables=frozenset({x}),
)
# Generate words up to 3 derivation steps
words = system.generate_words(max_steps=3)
print(sorted(words, key=lambda w: (len(w), w)))
# ['a', 'b', 'aa', 'ab', 'ba', 'bb', 'aaa', 'aab', 'aba', 'abb', ...]
Using the CLI
The package includes an interactive REPL for exploring systems without writing code:
$ pcs
Post Canonical Systems REPL v2.0.0
Type 'help' for commands.
pcs> alphabet MIU
Alphabet set: {I, M, U}
pcs> axiom MI
Axiom added: MI
pcs> var x
Variable added: x (ANY)
pcs> rule "$xI -> $xIU"
Rule added: $xI -> $xIU
pcs> rule "M$x -> M$x$x"
Rule added: M$x -> M$x$x
pcs> generate 3
Generated 8 words:
MI, MII, MIU, MIII, MIIU, MIIII, MIIIU, MIUIU
pcs> query "MU"
'MU' NOT_FOUND after exploring 10000 words
pcs> exit
Goodbye.
CLI Commands
| Command | Description |
|---|---|
alphabet <symbols> |
Set the alphabet (e.g., alphabet MIU) |
var <name> [kind] |
Add a variable (any, non_empty, single) |
axiom <word> |
Add an axiom (starting word) |
rule "<pattern>" |
Add a production rule |
show |
Display current system configuration |
generate <steps> |
Generate words up to N derivation steps |
query "<word>" |
Check if a word is reachable |
trace "<word>" |
Show the derivation path for a word |
save <file> |
Save system to JSON |
load <file> |
Load system from JSON |
clear |
Reset configuration |
SystemBuilder DSL
For ergonomic system construction, use the fluent builder API:
from post_canonical.builder import SystemBuilder
# Build the MU puzzle system
system = (SystemBuilder("MIU")
.var("x")
.var("y")
.axiom("MI")
.rule("$xI -> $xIU", name="add_U")
.rule("M$x -> M$x$x", name="double")
.rule("$x III $y -> $x U $y", name="III_to_U")
.rule("$x UU $y -> $x$y", name="delete_UU")
.build())
# Generate and explore
words = system.generate_words(max_steps=5)
print(f"Generated {len(words)} words")
Variable Syntax
Variables in rule patterns use $ prefix:
$name- Short form (ends at non-alphanumeric)${name}- Explicit form (for clarity)
Whitespace in patterns is ignored, so $x III $y is equivalent to ${x}III${y}.
Variable Kinds
builder = (SystemBuilder("abc")
.var("x") # ANY: matches "" or "a" or "abc"...
.var("y", kind="non_empty") # NON_EMPTY: matches "a" or "abc"...
.var("z", kind="single") # SINGLE: matches exactly one symbol
# ...
)
Visualization
Export derivations in multiple formats for documentation and analysis:
from post_canonical import create_mu_puzzle
from post_canonical.visualization import to_dot, to_latex, to_mermaid, to_ascii_tree
# Create system and generate words with derivations
system = create_mu_puzzle()
derived_words = system.generate(max_steps=3)
# Find a specific word's derivation
for dw in derived_words:
if dw.word == "MIIII":
# GraphViz DOT format
print(to_dot(dw.derivation))
# LaTeX proof format
print(to_latex(dw.derivation))
# Mermaid diagram
print(to_mermaid(dw.derivation))
# ASCII tree for terminal
print(to_ascii_tree(dw.derivation))
break
Example Output
ASCII Tree:
MIIII
+-- MII (double)
+-- MI (axiom)
Mermaid Diagram:
graph TD
MI -->|double| MII
MII -->|double| MIIII
GraphViz DOT:
digraph derivation {
"MI" -> "MII" [label="double"];
"MII" -> "MIIII" [label="double"];
}
Preset Systems
Ready-to-use example systems:
| System | Import | Description |
|---|---|---|
| MU Puzzle | create_mu_puzzle() |
Hofstadter's famous puzzle from GEB |
| Binary Doubler | create_binary_doubler() |
Doubles binary strings: 1 -> 11 -> 1111 |
| Palindrome Generator | create_palindrome_generator() |
Generates all binary palindromes |
Preset Alphabets
| Alphabet | Symbols |
|---|---|
BINARY |
01 |
DECIMAL |
0123456789 |
HEXADECIMAL |
0123456789ABCDEF |
ENGLISH_LOWERCASE |
a-z |
ENGLISH_UPPERCASE |
A-Z |
ENGLISH_LETTERS |
a-zA-Z |
MIU |
MIU |
from post_canonical import create_mu_puzzle, BINARY
from post_canonical.query import ReachabilityQuery
# Use the MU puzzle
mu = create_mu_puzzle()
print(mu.describe())
# Check if MU is derivable (spoiler: it's not!)
query = ReachabilityQuery(mu)
result = query.is_derivable("MU", max_words=10000)
print(result) # 'MU' NOT_FOUND after exploring 10000 words
API Reference
Core Classes
| Class | Description |
|---|---|
PostCanonicalSystem |
Main system class with generation and iteration methods |
Alphabet |
Defines valid symbols for the system |
Variable |
Pattern variables with matching constraints |
VariableKind |
Enum: ANY, NON_EMPTY, SINGLE |
Pattern |
Sequence of literals and variables |
ProductionRule |
Transformation rule with antecedents and consequent |
Derivation Classes
| Class | Description |
|---|---|
DerivedWord |
A word paired with its derivation history |
Derivation |
Complete chain of derivation steps |
DerivationStep |
Single rule application with bindings |
Query Classes
| Class | Description |
|---|---|
ReachabilityQuery |
Check if words are derivable from axioms |
ReachabilityResult |
Query result with status and derivation |
QueryResult |
Enum: DERIVABLE, NOT_FOUND |
Serialization
| Class | Description |
|---|---|
PCSJsonCodec |
Save/load systems to JSON files |
Builder
| Class | Description |
|---|---|
SystemBuilder |
Fluent DSL for constructing systems |
Requirements
- Python 3.12+
- No external dependencies
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file post_canonical-2.0.0.tar.gz.
File metadata
- Download URL: post_canonical-2.0.0.tar.gz
- Upload date:
- Size: 62.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8da3f81c695a885ecd6ef91569b759773ec9af0fcd48bb9d973bba6c9c5f66d1
|
|
| MD5 |
c9bbb4abe2a6fdd626b3fab76faa4066
|
|
| BLAKE2b-256 |
658048800753a4b933fdda65a1a003677a3adbcef5a7b3c210d02701d6b20f84
|
Provenance
The following attestation bundles were made for post_canonical-2.0.0.tar.gz:
Publisher:
publish.yml on niarenaw/post-canonical
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
post_canonical-2.0.0.tar.gz -
Subject digest:
8da3f81c695a885ecd6ef91569b759773ec9af0fcd48bb9d973bba6c9c5f66d1 - Sigstore transparency entry: 849722597
- Sigstore integration time:
-
Permalink:
niarenaw/post-canonical@13b2f840c681654fcb76c56bf32ebdba9c0ffe86 -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/niarenaw
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@13b2f840c681654fcb76c56bf32ebdba9c0ffe86 -
Trigger Event:
release
-
Statement type:
File details
Details for the file post_canonical-2.0.0-py3-none-any.whl.
File metadata
- Download URL: post_canonical-2.0.0-py3-none-any.whl
- Upload date:
- Size: 38.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aa26eebdec9d28d304dafef18116e624bc3308cd91b692e00098b62491c933c0
|
|
| MD5 |
2c606ebf8970a9d6fd0afe79b7cf6057
|
|
| BLAKE2b-256 |
b2437af205cf0953e118144e8835efca692ff59428b5d473a96d03fe2d5cee90
|
Provenance
The following attestation bundles were made for post_canonical-2.0.0-py3-none-any.whl:
Publisher:
publish.yml on niarenaw/post-canonical
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
post_canonical-2.0.0-py3-none-any.whl -
Subject digest:
aa26eebdec9d28d304dafef18116e624bc3308cd91b692e00098b62491c933c0 - Sigstore transparency entry: 849722601
- Sigstore integration time:
-
Permalink:
niarenaw/post-canonical@13b2f840c681654fcb76c56bf32ebdba9c0ffe86 -
Branch / Tag:
refs/tags/v2.0.0 - Owner: https://github.com/niarenaw
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@13b2f840c681654fcb76c56bf32ebdba9c0ffe86 -
Trigger Event:
release
-
Statement type: