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.2.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.2-py3-none-any.whl (14.6 kB view details)

Uploaded Python 3

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

Hashes for lsystems_core-0.1.2.tar.gz
Algorithm Hash digest
SHA256 64530b29a9c3831b1b91540c73a4c4ece93499a37941d370afdf4eb96724b82e
MD5 700cce0ff2545f3d188746900546c718
BLAKE2b-256 4a8d582b451683be568c4f445875f70c0d7a5d4fc1e31f8ddf9959eefebce5ab

See more details on using hashes here.

Provenance

The following attestation bundles were made for lsystems_core-0.1.2.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.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

Hashes for lsystems_core-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 aa120dd56dacc3ac2da157ff4a25ee75b54a5bd6526d7be72d94999af0b9650f
MD5 4f7a206a974ed4278578cb011efcb083
BLAKE2b-256 1831fad02d01f053d1a8649f5495f547e1db5f0df47658303e8773c57d7a9d1c

See more details on using hashes here.

Provenance

The following attestation bundles were made for lsystems_core-0.1.2-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