林登迈尔.jl:在 Julia 中定义递归模式
Lindenmayer.jl: Defining recursive patterns in Julia

原始链接: https://cormullion.github.io/Lindenmayer.jl/stable/

## Lindenmayer.jl:使用L系统生成分形 Lindenmayer.jl 是一个 Julia 包,利用 Luxor.jl 创建 L 系统——最初开发用于模拟植物生长的基于规则的系统。L 系统通过应用于初始状态(“公理”)的一组替换规则递归地定义模式。 一个 L 系统由规则(搜索和替换对)和起始状态定义。例如,一个规则可以将 "F" 替换为 "G+F+Gt"。`drawLSystem()` 函数然后将演化状态中的字符解释为 Luxor.jl 乌龟的绘图指令——"F" 和 "G" 向前移动,"+" 和 "-" 旋转,数字控制线条宽度。 除了植物建模,L 系统还可以生成自相似分形。该包通过 `drawLSystem()` 中的关键字参数提供广泛的自定义选项,控制前向距离、旋转角度、迭代次数和输出文件名等参数。高级功能包括在绘图期间执行的自定义函数(使用“*”字符)以及用于集成到现有 Luxor 工作流程的单独的 `evaluate()` 和 `render()` 函数。调试通过 `JULIA_DEBUG` 环境变量支持。 Lindenmayer.jl 提供了一个灵活的工具,用于探索递归模式和分形几何的美丽和复杂性。

黑客新闻 新的 | 过去的 | 评论 | 提问 | 展示 | 工作 | 提交 登录 Lindenmayer.jl: 在 Julia 中定义递归模式 (cormullion.github.io) 6 分,作者 WillMorr 2 小时前 | 隐藏 | 过去的 | 收藏 | 1 条评论 帮助 AxiomLab 9 分钟前 [–] L 系统完美地说明了如何从少量的离散公理中数学地推导出巨大的视觉复杂性。这是一个核心的心理模型。在构建生成系统时,设计者的工作从手动绘制最终形状,根本性地转变为严格地设计初始递归规则。无法表达为递归函数的系统通常只是一种任意的样式,而不是真正的系统。回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

This is a simple package that can make LSystems. It uses Luxor.jl to draw them.

An LSystem, or Lindenmayer system, is a set of rules that can define recursive patterns.

These were introduced and developed in 1968 by Aristid Lindenmayer, a Hungarian theoretical biologist and botanist at the University of Utrecht. Lindenmayer used LSystems to describe the behaviour of plant cells and to model the growth processes of plant development. LSystems have also been used to model the morphology of a variety of organisms and can be used to generate self-similar fractals such as iterated function systems.

In Lindenmayer.jl you can define an LSystem like this:

sierpinski_triangle = LSystem([
        "F" => "G+F+Gt",
        "G" => "F-G-F"],
    "G")

This one has two rules, and an initial state. You can draw it using the drawLSystem() function.

For example:

using Lindenmayer
sierpinski_triangle  = LSystem([
        "F" => "G+F+Gt",
        "G" => "7F-G-F"
    ],
    "G")

drawLSystem(sierpinski_triangle,
    forward     = 10,
    turn        = 60,
    iterations  = 6,
    startingx   = -300,
    startingy   = -300,
    filename    = :svg)
Example block output

In Lindenmayer.jl, an LSystem consists of:

  • Rules: one or more search and replace rules in a Vector. Each rule replaces a single-character string with a string of one or more characters

  • Initial state: the initial seed state for the system (sometimes called "the Axiom")

  • State: the current evolved state (initially empty, added when the system is evaluated)

The sierpinski_triangle LSystem has two rules. The first rule says replace "F" with "G+F+Gt" at every iteration. Rule 2 says replace "G" with "F-G-F" at every iteration. We start off with an initial state consisting of just a single "G".

So the system State grows like this:

1: G
2: (F-G-F) # after applying rule 2
3: (G+F+G)-(F-G-F)-(G+F+G) # after applying rule 1
4: (F-G-F)+(G+F+G)+(F-G-F)-(G+F+G)-(F-G-F)-(G+F+G)-(F-G-F)+(G+F+G)+(F-G-F)
5: (G+F+G)-(F-G-F)-(G+F+G)+(F-G-F)+(G+F+G)+(F-G-F)+(G+F+G)-(F-G-F)-(G+F+G)-(F-G-F)+(G+F+G)+(F-G-F)-(G+F+G)-(F-G-F)-(G+F+G)-(F-G-F)+(G+F+G)+(F-G-F)-(G+F+G)-(F-G-F)-(G+F+G)+(F-G-F)+(G+F+G)+(F-G-F)+(G+F+G)-(F-G-F)-(G+F+G)
6: (F-G-F)+(G+F+G)+(F-G-F)-(G+F+G)-(F-G-F)-(G+F+G)-(F-G-F)+(G+F+G)+(F-G-F)+(G+F+G)-(F-G-F)-(G+F+G)+(F-G-F)+(G+F+G)+(F-G-F)+(G+F+G)-(F-G-F)-(G+F+G)+(F-G-F)+(G+F+G)+(F-G-F)-(G+F+G)-(F-G-F)-(G+F+G)-(F-G-F)+(G+F+G)+(F-G-F)-(G+F+G)-(F-G-F)-(G+F+G)+(F-G-F)+(G+F+G)+(F-G-F)+(G+F+G)-(F-G-F)-(G+F+G)-(F-G-F)+(G+F+G)+(F-G-F)-(G+F+G)-(F-G-F)-(G+F+G)-(F-G-F)+(G+F+G)+(F-G-F)-(G+F+G)-(F-G-F)-(G+F+G)+(F-G-F)+(G+F+G)+(F-G-F)+(G+F+G)-(F-G-F)-(G+F+G)-(F-G-F)+(G+F+G)+(F-G-F)-(G+F+G)-(F-G-F)-(G+F+G)-(F-G-F)+(G+F+G)+(F-G-F)+(G+F+G)-(F-G-F)-(G+F+G)+(F-G-F)+(G+F+G)+(F-G-F)+(G+F+G)-F-G-F-(G+F+G)+F-G-F+(G+F+G)+F-G-F-(G+F+G)-F-G-F-(G+F+G)-F-G-F+(G+F+G)+F-G-F... etc.

and, afer only a few iterations, the state consists of thousands of instructions.

Use drawLSystem() to evaluate and draw the LSystem. The characters in the rule are interpreted as instructions to control a Luxor.jl turtle.

  • "F" and "G" both convert to Luxor.Forward()

  • "+" rotates the turtle clockwise

  • "-" rotates the turtle counterclockwise

  • "5" specifies a 5 pt thick line

  • "t" shifts the pen's hue color by 5°

The actual distance moved by "F" and "G" instructions, the angle of the turn, and other starting parameters, are specified when you evaluate the LSystem.

The following characters are turtle-ese, referring to existing instructions:

& * + - 1 2 3 4 5 6 7 8 9 @ 
B D F G O T U V [ ] 
b c f l n o q r s t

You can use the remaining letters as placeholders or variables as you like. For example, the following Hilbert LSystem uses L and R, which don't do anything on their own - but they do expand to use plenty of "F", "+", and "-" rules.

hilbert_curve = LSystem([
   "L" => "+RF-LFL-FR+",
   "R" => "-LF+RFR+FL-"
   ],
   "3L") # 90°

To evaluate and draw the LSystem, use drawLSystem().

drawLSystem(LSystem(["F" => "5F+F--F+Ftt"], "F"),
    startingx = -400,
    forward = 4,
    turn = 80,
    iterations = 6)

Keyword options and defaults for drawLSystem are:

forward              = 15,
turn                 = 45,
iterations           = 10,
filename             = "/tmp/lsystem.png",
width                = 800,
height               = 800,
startingpen          = (0.3, 0.6, 0.8), # starting color in RGB
startingx            = 0,
startingy            = 0,
startingorientation  = 0,
backgroundcolor      = colorant"black",
asteriskfunction     = (t::Turtle) -> (),
showpreview          = true

The following characters are recognized in LSystem rules.

Character in ruleFunction
-turn backwards by angle
[push the current state on the stack
]pop the current state off the stack
@turn 5°
*execute the supplied function
&turn -5°
+turn by angle (degrees!)
1set line width to 1
2set line width to 2
3set line width to 3
4set line width to 4
5set line width to 5
6set line width to 6
7set line width to 7
8set line width to 8
9set line width to 9
Bstep backwards
bturn 180° and take half a step forward
crandomize the saturation
Dpen down (start drawing)
fhalf a step forward
Fstep Forward
Gsame as F
lincrease the step size by 1
nset line width to 0.5
Ochoose a random opacity value
odraw a circle with radius step/4
qdraw a square with side length step/4
rturn randomly by 10° 15° 30° 45° or 60°
sdecrease the step size by 1
Tchange the hue at random
tshift the hue by 5°
Ulift the pen (stop drawing)
Vsame as B

You can define one external function in an LSystem. Whenever you include the * character in a rule, a function passed to drawLSystem() using the keyword option asteriskfunction will be called. This function accesses the Luxor turtle that's currently busy drawing the LSystem.

In the next example, a circle is drawn whenever the evaluation encounters a *. The advantage of using this (rather than the o) is that the radius of the circle can be made to vary with the distance from the center.

phyllotax = LSystem(["A" => "A+[UFD*]ll"], "A")

counter = 0
f(t::Turtle) = begin
   global counter
   fontsize(22)
   d = distance(O, Point(t.xpos, t.ypos))
   sethue(HSL(mod(counter, 360), 0.8, 0.5))
   circle(Point(t.xpos, t.ypos), rescale(d, 1, 200, 3, 15), :fill)
   counter += 1
end

drawLSystem(phyllotax,
   forward=65,
   turn=137.5,
   iterations=200,
   startingx=0,
   startingy=0,
   width=1000,
   height=1000,
   filename=:png,
   asteriskfunction=f
)
Example block output

In the next example, the asterisk function f(t::Turtle) passed to drawLSystem() is a bit disruptive. It changes the line width, sets the color, and then draws a group of rescaled pentagons at the turtle's current location and other rotationally symmetrical places. Then, it sets the opacity to 0. The turtle never realises this and never resets it (the t hue-shifting rule uses Luxor.sethue() which doesn't change the current opacity). So all the lines drawn by the turtle are completely transparent, leaving just the pentagons visible.

using Lindenmayer, Luxor, Colors

recursive = LSystem([
   "F" => "G+F+G6t",
   "G" => "F*-G-F"
    ],
   "G2")

f(t::Turtle) = begin
    p = Point(t.xpos, t.ypos)
    setline(3)
    setopacity(1)
    setcolor(HSB(rand(0:359), 0.7, 0.7))
    for i in 0:4
        @layer begin
            rotate(i * deg2rad(72))
            ngon(p, rescale(distance(p, O), 1, 1000, 3, 20), 5, 0, :stroke)
        end
    end
    setopacity(0.0)
end

drawLSystem(recursive,
    forward=10,
    turn=72,
    iterations= 7,
    startingx = 0,
    startingy = 0,
    width=800,
    height=1000,
    backgroundcolor = colorant"black",
    filename=:png,
    asteriskfunction = f)
Example block output

drawLSystem() has plenty of options, but you might prefer to use an LSystem in a regular Luxor workflow. To do this, use the Lindenmayer.evaluate() and Lindenmayer.render() functions separately.

After Lindenmayer.evaluate() has run, the LSystem struct has all the turtle operations stored (as UInt16 integers) in the .state field. Lindenmayer.render() can convert these to Luxor turtle instructions.

using Lindenmayer
using Luxor
using Colors

@drawsvg begin
   background("black")
   setlinecap("round")
   penrose = LSystem(Dict("X" => "PM++QM----YM[-PM----XM]++t",
         "Y" => "+PM--QM[---XM--YM]+t",
         "P" => "-XM++YM[+++PM++QM]-t",
         "Q" => "--PM++++XM[+QM++++YM]--YMt",
         "M" => "F",
         "F" => ""),
      "[Y]++[Y]++[Y]++[Y]++[Y]")

   # evaluate the LSystem
   Lindenmayer.evaluate(penrose, 5)

   # create a turtle
   🐢 = Turtle()
   Penwidth(🐢, 5)
   Pencolor(🐢, "cyan")

   # render the LSystem's evaluation to the drawing;
   # forward step is 45
   # turn angle is 36°
   Lindenmayer.render(penrose, 🐢, 45, 36)
end 800 800
Example block output

To debug:

ENV["JULIA_DEBUG"] = Lindenmayer

To stop debugging:

ENV["JULIA_DEBUG"] = nothing
Documentation built 2025-07-08T14:20:35.201 with Julia 1.11.5 on Linux
联系我们 contact @ memedata.com