Skip to main content

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.String
  • lsystems.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:

  1. iterate through the current sentence
  2. retrieve the production for each symbol
  3. build a ScopeBundle for that symbol
  4. rewrite each symbol into a sentence
  5. 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 A to X when it appears at the start of the sentence and is followed by B.

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:

  • run
  • generation
  • position

These scopes provide contextual information during rewriting.

Run scope

Created once for the full derivation.

Fields:

  • name
  • lsystem
  • seed
  • rng

Generation scope

Created once per generation.

Fields:

  • depth
  • generation
  • sentence

Position scope

Created once per symbol.

Fields:

  • index
  • symbol

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


Download files

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

Source Distribution

lsystems_core-0.1.0.tar.gz (13.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lsystems_core-0.1.0-py3-none-any.whl (14.6 kB view details)

Uploaded Python 3

File details

Details for the file lsystems_core-0.1.0.tar.gz.

File metadata

  • Download URL: lsystems_core-0.1.0.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

Hashes for lsystems_core-0.1.0.tar.gz
Algorithm Hash digest
SHA256 18b862c10c8e678dad32a2214ebe32f4068b3b349e59fb336134213d33a6e381
MD5 de68f64ca651d796fd39f9c7e1458aa3
BLAKE2b-256 85674cef68b27d600b66845a3452635b86c7cc2dd465252aaad15d82c1a72955

See more details on using hashes here.

Provenance

The following attestation bundles were made for lsystems_core-0.1.0.tar.gz:

Publisher: publish.yml on OpenJ92/l-systems

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lsystems_core-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: lsystems_core-0.1.0-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

Hashes for lsystems_core-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dc97ef655d9e4d2b6942085cdd915d2ab2bc5f409f8cc44da73e7c8ef18dfa97
MD5 d39ce18e221fd36d8e254398844713c4
BLAKE2b-256 0e32527a1ed0e82114a910ff75d0f0fc8e2da73daafa7a6657569abba47c2097

See more details on using hashes here.

Provenance

The following attestation bundles were made for lsystems_core-0.1.0-py3-none-any.whl:

Publisher: publish.yml on OpenJ92/l-systems

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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