A compositional L-system framework for generative structures and symbolic rewriting in Python.
Project description
lsystems
A small, compositional Python library for defining and generating L-systems.
This project is built around a simple idea:
- a sentence is an iterable monoidal container of symbols,
- a production rewrites one symbol into a new sentence,
- a generator applies productions across generations,
- and a scope system carries contextual information into each rewrite.
The result is a compact framework for deterministic, stochastic, and context-sensitive rewriting that is already useful for procedural generation, grammar experiments, and generative art pipelines.
Current status
Implemented now:
- deterministic productions
- stochastic productions
- context-sensitive productions
- precedence-based production composition
- generic sentence protocol
- string-backed and tuple-backed sentence types
- scoped generation with run / generation / position context
- fallback identity rewrites for symbols without explicit productions
Planned next:
- interpreters (for example turtle or geometry backends)
- richer reproducible randomness via scoped RNGs
- tests and more examples
Why this project exists
Most L-system examples are written as one-off scripts over strings. That is fine for a toy system, but it becomes limiting when you want to:
- swap out the underlying sentence representation,
- attach contextual information to rewrites,
- mix deterministic and stochastic rules,
- experiment with non-string symbol types,
- or treat rewriting as part of a larger compositional system.
lsystems tries to keep the core model small while making those extensions natural.
Design overview
The package is centered on four concepts.
1. Sentence
A sentence is any type that behaves like an iterable sequence of symbols and supports a monoidal interface:
empty()lift(symbol)combine(other)clone()
This lets the generator stay generic. A rewrite step does not care whether it is operating on a str, a tuple, or some future structured sentence type.
Currently included:
lsystems.sentences.string.Stringlsystems.sentences.tuple.Tuple
2. Production
A production is anything callable with the shape:
production(symbol, scope) -> Sentence
This is intentionally minimal. It means a production can be:
- a constant rewrite
- a stochastic chooser
- a context-sensitive rule
- a parametric rule
- or a production that inspects the current run, generation, or position
3. Productions
Productions stores the mapping from symbols to production objects.
If a symbol has no registered production, the system falls back to an identity-style rewrite by lifting the symbol back into the sentence type.
In other words, unspecified symbols persist automatically.
4. Generate
Generate performs the derivation over a fixed number of generations.
For each generation:
- iterate through the current sentence
- retrieve the production for each symbol
- build a
ScopeBundlefor that symbol - rewrite each symbol into a sentence
- combine all rewrites into the next sentence
This makes the derivation pipeline explicit and easy to extend.
Package layout
src/lsystems/
├── __main__.py
├── generate.py
├── lsystem.py
├── productions/
│ ├── productions.py
│ ├── static.py
│ ├── stochastic.py
│ ├── precedence.py
│ └── context.py
├── protocols/
│ ├── production.py
│ └── sentence.py
└── sentences/
├── string.py
└── tuple.py
Installation
From the project root:
pip install .
For development:
pip install -e .[dev]
Quick start
from lsystems.sentences.string import String
from lsystems.productions.static import Static
from lsystems.productions.productions import Productions
from lsystems.lsystem import LSystem
from lsystems.generate import Generate
sentence = String("X")
productions = Productions(String)
productions.add("X", Static(String("F+[[X]-X]-F[-FX]+X")))
productions.add("F", Static(String("FF")))
alphabet = set("FX+-[]")
lsys = LSystem(alphabet, productions, sentence)
result = Generate(lsys, depth=2).run()
print(result)
Production types
Static productions
Static always returns the same rewrite sentence.
from lsystems.productions.static import Static
from lsystems.sentences.string import String
Static(String("AB"))
Stochastic productions
Stochastic selects a rewrite using weighted probabilities.
from lsystems.productions.stochastic import Stochastic
from lsystems.sentences.string import String
rule = Stochastic()
rule.add(3, String("AA"))
rule.add(1, String("AB"))
Internally this is stored using cumulative cutoffs and sampled with bisect.
Precedence productions
Precedence allows multiple production strategies to be layered.
Productions are evaluated in order, and the first one that resolves is used.
from lsystems.productions.precedence import Precedence
from lsystems.productions.static import Static
from lsystems.productions.stochastic import Stochastic
from lsystems.sentences.string import String
context_rule = Static(String("X"))
fallback = Stochastic()
fallback.add(3, String("AA"))
fallback.add(1, String("AB"))
production = Precedence(
context_rule,
fallback
)
This makes it easy to express rules like:
context rule
↓
stochastic rule
↓
identity
Context-sensitive productions
ContextSensitive matches rules based on surrounding symbols.
Example rule:
empty > A < B → X
Meaning:
rewrite
AtoXwhen it appears at the start of the sentence and is followed byB.
Example implementation:
from lsystems.productions.context import ContextSensitive
from lsystems.sentences.string import String
rule = ContextSensitive(0, 1)
rule.add(String(""), String("B"), String("X"))
If no rule matches the surrounding context, the symbol is preserved.
Variational context-sensitive productions
VariationalContextSensitive combines several context rules of different widths.
Wider contexts automatically take precedence.
Example:
CA > B < CA → Z
A > B < A → Y
empty > B < A → X
Example implementation:
from lsystems.productions.context import (
ContextSensitive,
VariationalContextSensitive
)
from lsystems.sentences.string import String
ctx_22 = ContextSensitive(2,2)
ctx_22.add(String("CA"), String("CA"), String("Z"))
ctx_11 = ContextSensitive(1,1)
ctx_11.add(String("A"), String("A"), String("Y"))
ctx_01 = ContextSensitive(0,1)
ctx_01.add(String(""), String("A"), String("X"))
production = VariationalContextSensitive(
ctx_22,
ctx_11,
ctx_01
)
Scope system
Each production receives a ScopeBundle containing:
rungenerationposition
These scopes provide contextual information during rewriting.
Run scope
Created once for the full derivation.
Fields:
namelsystemseedrng
Generation scope
Created once per generation.
Fields:
depthgenerationsentence
Position scope
Created once per symbol.
Fields:
indexsymbol
Running the examples
The repository includes several working examples in:
src/lsystems/__main__.py
Run them with:
python -m lsystems
Development philosophy
The project aims for a balance:
- small enough to understand completely
- generic enough to extend
- compositional enough to integrate with other systems
Future directions
Possible extensions include:
- parametric symbols
- turtle graphics interpreters
- grammar-driven production synthesis
- parser-based rule matching
- additional sentence container types
License
See LICENSE.
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 lsystems_core-0.1.2.tar.gz.
File metadata
- Download URL: lsystems_core-0.1.2.tar.gz
- Upload date:
- Size: 13.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
64530b29a9c3831b1b91540c73a4c4ece93499a37941d370afdf4eb96724b82e
|
|
| MD5 |
700cce0ff2545f3d188746900546c718
|
|
| BLAKE2b-256 |
4a8d582b451683be568c4f445875f70c0d7a5d4fc1e31f8ddf9959eefebce5ab
|
Provenance
The following attestation bundles were made for lsystems_core-0.1.2.tar.gz:
Publisher:
publish.yml on OpenJ92/l-systems
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lsystems_core-0.1.2.tar.gz -
Subject digest:
64530b29a9c3831b1b91540c73a4c4ece93499a37941d370afdf4eb96724b82e - Sigstore transparency entry: 1187773626
- Sigstore integration time:
-
Permalink:
OpenJ92/l-systems@b1a6d327b07dd560e1519b4634a53c15ecbce66c -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/OpenJ92
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b1a6d327b07dd560e1519b4634a53c15ecbce66c -
Trigger Event:
push
-
Statement type:
File details
Details for the file lsystems_core-0.1.2-py3-none-any.whl.
File metadata
- Download URL: lsystems_core-0.1.2-py3-none-any.whl
- Upload date:
- Size: 14.6 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 |
aa120dd56dacc3ac2da157ff4a25ee75b54a5bd6526d7be72d94999af0b9650f
|
|
| MD5 |
4f7a206a974ed4278578cb011efcb083
|
|
| BLAKE2b-256 |
1831fad02d01f053d1a8649f5495f547e1db5f0df47658303e8773c57d7a9d1c
|
Provenance
The following attestation bundles were made for lsystems_core-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on OpenJ92/l-systems
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lsystems_core-0.1.2-py3-none-any.whl -
Subject digest:
aa120dd56dacc3ac2da157ff4a25ee75b54a5bd6526d7be72d94999af0b9650f - Sigstore transparency entry: 1187773633
- Sigstore integration time:
-
Permalink:
OpenJ92/l-systems@b1a6d327b07dd560e1519b4634a53c15ecbce66c -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/OpenJ92
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b1a6d327b07dd560e1519b4634a53c15ecbce66c -
Trigger Event:
push
-
Statement type: