Symbios
A Sovereign Derivation Engine for Parametric L-Systems.
Symbios is a pure-Rust, high-performance engine for generating Lindenmayer Systems. It is designed for "Sovereign" applications where the logic must run locally, deterministically, and safely (e.g., WASM environments, embedded simulation).
It fully implements the syntax and semantics described in The Algorithmic Beauty of Plants (Prusinkiewicz & Lindenmayer, 1990).
Key Features
- Structure-of-Arrays (SoA): Data layout optimized for cache locality and WASM memory limits.
- Parametric & Context-Sensitive: Full support for
(k,l)-systems, arithmetic guardsA(x) : x > 5 -> ..., and variable binding. - Genetic Algorithm Toolkit: 9 mutation operators, 4 crossover strategies, and lossless source round-tripping for evolutionary optimization.
- Adversarial Hardening: Protected against recursion bombs, memory exhaustion, and floating-point fragility.
- Deterministic: Seedable RNG (
rand_pcg) ensures reproducible procedural generation. - Zero
unsafeCode: All safety via Rust's type system and explicit bounds checks.
Usage
[]
= "1.4"
use System;
SourceGenotype (Evolvable Wrapper)
For evolutionary loops that operate at the source-text level:
use SourceGenotype;
use ;
use SeedableRng;
use Pcg64;
let mut genotype = new;
let mut rng = seed_from_u64;
// Mutate in source space
genotype.mutate_with_rng.unwrap;
// Crossover two genotypes
let other = new;
let child = genotype.crossover_with_rng.unwrap;
// Realize into a runnable System
let sys = child.to_system.unwrap;
Genetic Algorithm Support
Symbios includes a comprehensive toolkit for evolutionary optimization of L-Systems.
Basic Mutation
use ;
let mut sys = new;
sys.add_rule.unwrap;
sys.add_rule.unwrap;
sys.add_directive.unwrap;
// Mutate rule probabilities and constants
let config = MutationConfig ;
sys.mutate;
// Structural mutation: insert/delete/swap modules, perturb bytecode
let structural_config = StructuralMutationConfig ;
sys.structural_mutate;
Advanced Mutation Operators
use ;
// Operator flip: swap semantically-paired operators (Add<->Sub, Gt<->Lt, etc.)
sys.operator_flip_mutate;
// Rule duplication: clone a rule, split probability, perturb the copy
sys.rule_duplication_mutate;
// Topological mutation: swap turtle command pairs (+/-, &/^, F/f, \//)
sys.topological_mutate;
// Literal-to-constant promotion: replace Push literals with matching #define values
sys.literal_to_constant_promote;
Crossover
use ;
let mut parent_a = new;
parent_a.add_rule.unwrap;
parent_a.add_directive.unwrap;
let mut parent_b = new;
parent_b.add_rule.unwrap;
parent_b.add_directive.unwrap;
// Basic crossover: select whole rule sets per symbol, blend constants
let basic_config = CrossoverConfig ;
let offspring = parent_a.crossover.unwrap;
// Advanced crossover: homologous rule selection + BLX-alpha blending
let advanced_config = AdvancedCrossoverConfig ;
let offspring = parent_a.advanced_crossover.unwrap;
// Sub-expression grafting: swap balanced branch blocks [...]
let offspring = parent_a.subexpression_graft.unwrap;
Reproducibility
All mutation and crossover operations have _with_rng variants that accept an external RNG for deterministic results:
use SeedableRng;
use Pcg64;
let mut rng = seed_from_u64;
sys.mutate_with_rng;
sys.structural_mutate_with_rng;
sys.operator_flip_mutate_with_rng;
sys.rule_duplication_mutate_with_rng;
sys.topological_mutate_with_rng;
sys.literal_to_constant_promote_with_rng;
let offspring = parent_a.crossover_with_rng;
let offspring = parent_a.advanced_crossover_with_rng;
let offspring = parent_a.subexpression_graft_with_rng;
Rule Export
Symbios can decompile compiled rules back to source text. This is useful for inspecting mutated rules, serialization, and debugging.
Export All Rules
use System;
let mut sys = new;
sys.add_rule.unwrap;
sys.add_rule.unwrap;
// Export all rules as (predecessor, source) pairs
for in sys.export_rules
Export Rules for a Specific Symbol
let rules = sys.export_rules_for;
for rule in rules
Export a Specific Rule
let rule = sys.export_rule_at.unwrap;
println!;
Custom Parameter Names
By default, exported rules use synthetic parameter names (p0, p1, ...). You can provide custom names:
let rule = sys.export_rule_with_params.unwrap;
println!;
// Output: A(age) : age > 10 -> B(age + 1)
Directives
// Constants: define reusable numeric values
sys.add_directive?;
sys.add_directive?;
// Ignore: symbols to skip during context matching
sys.add_directive?;
Stochastic Rules
Rules can have probability weights for stochastic selection:
// Probability prefix syntax
sys.add_rule?;
sys.add_rule?;
// Set seed for deterministic results
sys.set_seed;
sys.derive?;
When multiple rules match a module, one is selected with probability proportional to its weight. A single matching rule always fires regardless of its probability value.
Temporal Dynamics
Modules track their age (time since creation). The age variable is available in conditions and expressions:
sys.add_rule?;
sys.set_axiom?;
// Advance time and derive
sys.state.advance_time?;
sys.derive?;
Performance
Symbios uses a flat memory arena for parameters and u16 symbol interning.
- Rule Matching: $O(N)$ (HashMap bucketed)
- Context Matching: $O(1)$ (Topology Skip-Links)
See PERFORMANCE.md for detailed benchmarks and optimization tips.
Documentation
- ARCHITECTURE.md - System design, SoA layout, VM architecture, and design decisions
- PERFORMANCE.md - Benchmark results, optimization tips, and profiling guide
- TROUBLESHOOTING.md - Common errors, solutions, and debugging patterns
Examples
See examples/ for complete working examples:
- anabaena.rs - Simple discrete L-System from ABOP
- monopodial_tree.rs - Complex tree with branches and constants
- stochastic_decay.rs - Stochastic rule demonstration
- adaptive_plant.rs - Advanced example with age, context, and environment
License
MIT