Python中的分形。演练

哈Ha!今天关于分形的文章是作为Python主题(尤其是Matplotlib)的一部分出现的。遵循作者的示例,并警告说,帖子中有很多沉重的动画甚至可能无法在移动设备上运行。但是,多么美丽。



所有人都喜欢阅读

分形很漂亮。它们按照非常复杂的图案排列,并且在任何放大倍率下都不会变形!在本文中,我们将研究如何使用称为L-Systems的工具和Python的Turtle模块轻松地绘制几种分形,以逐步进行绘制。

在本文中,我们不会过多地讨论技术细节;相反,我仅作简要介绍,展示许多动画示例和代码,您可以使用这些示例和代码生成这样的示例。如果您想跳过理论而只看动画,请直接转到示例。此外,最后,我将指出您可能想探索的一些资源,包括代码编写和基础数学。

什么是分形?

首先,让我们给出一个分形的“松散”定义。原则上,分形是一个几何图形,无论增加的程度如何,它都具有相同的特性。

这个定义并不完美,因此以下是Math World网站提供的更准确的定义:
分形是在任何尺度上都表现出自相似性的对象或数量。一个物体在不同的尺度上显示出不同的结构,但是相同的“类型”结构必须出现在分形的所有水平上。在这种情况下,将图形绘制在具有对数刻度的坐标系中,其中沿轴计算幅度和比例,然后图形是一条直线,其斜率反映了分形的维数。- 数学世界

如何使用Python绘制分形?

通常,渲染分形很复杂,因为分形的深层性质由递归概念决定。在谈到图形及其绘制时,我们通常认为它们是由像素或矢量形成的,但是像素或矢量的数量始终受到限制,分形从定义上说是无限递归的。因此,尝试将分形应用于坐标网格,我们将不得不在某个点处停止,这就是为什么在这种情况下我们谈论“迭代”的原因。在每次迭代中,分形变得更加复杂,并且在某个点上变得无法区分彼此相邻的两次迭代(这种情况发生在当变化发生在与像素大小相当的水平时)。在这里停下来是合乎逻辑的,但是通常,分形的形状会更快出现,甚至可以更早停下来。

两个这样的示例是Koch的方形岛,它的结构在经过3次迭代后就清楚地显现出来;还有Carter-Heitway龙,对于它们,经过8次迭代就可以构建完整的结构。所需的迭代次数高度取决于我们正在使用的特定分形。

当然,有许多用于绘制的Python库,其中最受欢迎的是Matplotlib,但是它们通常是为绘制统计数据和绘制知名图形而设计的。尤其是Matplotlib包含一些可用于构造分形的低级构造,但是这次我们将集中讨论标准库称为Turtle的鲜为人知的模块。

龟模块

Python文档我们读到:“对于初次接触编程的孩子,海龟图形是一种流行的工具。他是由WallyFörzeg和Seymour Papert在1966年开发的原始徽标编程语言的一部分。”

最重要的是,默认情况下,乌龟可以识别3个命令:

  • 向前爬行
  • 旋转左角
  • 直角旋转

注意:标准库中提供了其他命令,但是在这里我们仅使用这三个命令。

我们还可以:

  • 静音记录
  • 启用录音

这些特征似乎太简单,无法仅依靠它们来绘制像分形这样的复杂图形,但是我们将使用另一种仅使用这一小部分指令的工具。我说的是L系统。

L系统

L系统是一种将递归结构(例如,分形)表示为字符串,并多次重写此字符串的方法。同样,我们给出一个正式定义:
Lindenmeyer系统,也称为L系统,是一种线重写机制,可用于生成尺寸从1到2的分形-Math World

了解了L系统是什么之后,我们可以创建递归结构,但首先让我们弄清楚为此需要哪些组件。每个L系统具有:

  • : , L-.
  • : .
  • : , .

致计算机科学爱好者的注意事项:如果您已深入学习计算机科学,那么以上所有内容可能会让您想起某些事情。确实,形式语法的定义非常相似。关键区别在于,与语法不同,此处每次迭代都应用尽可能多的规则,而不仅仅是一个。因此,L系统是上下文无关文法的子集。

假定我们要使用Turtle来构建图形,并使用L系统来表示要绘制的内容,我们需要在它们之间创建关系。

由于在Turtle中,我们只有上面列出的团队,因此我们为每个团队分配一个符号;字母将由这些字符组成。

  • F:向前爬行
  • +:向右转
  • -: 转左

为此,必须为每个分形提供一个角度。这将是乌龟向右或向左旋转的角度。为简单起见,我们同意只应提供一个角落,因此我们将牢记这一点编写L系统。
创建字符串的公理和指令将仅取决于分形,但是分形必须以只能由这三个字符表示的方式编写。因此存在一个限制,据此我们只能建立单线分形,即无法以这种方式获得类似Cantor集的东西。但这只是一个简化,因为我们总是可以输入另外两个命令来前进而不记录,而对于后退同样可以输入。

现在让我们来看例子!

动画示例

以下示例是从几个可公开获得的资源在线获得的,我决定使用Turtle模块将它们移植到Python,对其进行居中,着色以及提供一种导出为矢量格式的方法。

注意:建议的动画很大,建议仅在良好的互联网环境下观看。 Repl代码可能不起作用,因为它会占用您的资源,并且在移动设备上显示分形可能会出现问题。

注意:Skulpt使用您的浏览器来渲染和创建动画,因此当您挂起,滞后或任何奇怪的行为时,通常只需重播动画或重新加载页面就足够了。可能不适用于移动设备。

这些示例是按照复杂性顺序给出的(根据我的主观观点),因此最有趣的是最后。

科赫雪花

axiom = "F--F--F"
rules = {"F":"F+F--F+F"}
iterations = 4 # TOP: 7
angle = 60


科克广场岛

axiom = "F+F+F+F"
rules = {"F":"F-F+F+FFF-F-F+F"}
iterations = 2 # TOP: 4
angle = 90


水晶

axiom = "F+F+F+F"
rules = {"F":"FF+F++F+F"}
iterations = 3 # TOP: 6
angle = 90


方形雪花

axiom = "F--F"
rules = {"F":"F-F+F+F-F"}
iterations = 4 # TOP: 6
angle = 90


分形维切卡

axiom = "F-F-F-F"
rules = {"F":"F-F+F+F-F"}
iterations = 4 # TOP: 6
angle = 90


征税曲线

axiom = "F"
rules = {"F":"+F--F+"}
iterations = 10 # TOP: 16
angle = 45


谢尔宾斯基地毯

axiom = "YF"
rules = {"X":"YF+XF+Y", "Y":"XF-YF-X"}
iterations = 1 # TOP: 10
angle = 60


谢尔宾斯基格子

axiom = "FXF--FF--FF"
rules = {"F":"FF", "X":"--FXF++FXF++FXF--"}
iterations = 7 # TOP: 8
angle = 60


广场

axiom = "F+F+F+F"
rules = {"F":"FF+F+F+F+FF"}
iterations = 3 # TOP: 5
angle = 90


瓷砖

axiom = "F+F+F+F"
rules = {"F":"FF+F-F+F+FF"}
iterations = 3 # TOP: 4
angle = 90


戒指

axiom = "F+F+F+F"
rules = {"F":"FF+F+F+F+F+F-F"}
iterations = 2 # TOP: 4
angle = 90


十字架2

axiom = "F+F+F+F"
rules = {"F":"F+F-F+F+F"}
iterations = 3 # TOP: 6
angle = 90


五重性

axiom = "F++F++F++F++F"
rules = {"F":"F++F++F+++++F-F++F"}
iterations = 1 # TOP: 5
angle = 36


32段曲线

axiom = "F+F+F+F"
rules = {"F":"-F+F-F-F+F+FF-F+F+FF+F-F-FF+FF-FF+F+F-FF-F-F+FF-F-F+F+F-F+"}
iterations = 3 # TOP: 3
angle = 90


皮亚诺·戈斯珀曲线

axiom = "FX"
rules = {"X":"X+YF++YF-FX--FXFX-YF+", "Y":"-FX+YFYF++YF+FX--FX-Y"}
iterations = 4 # TOP: 6
angle = 60


谢尔宾斯基曲线

axiom = "F+XF+F+XF"
rules = {"X":"XF-F+F-XF+F+XF-F+F-X"}
iterations = 4 # TOP: 8
angle = 90


克里希纳的小问题

axiom = " -X--X"
rules = {"X":"XFX--XFX"}
iterations = 3 # TOP: 9
angle = 45


戈斯珀广场分形

axiom = "YF"
rules = {"X": "XFX-YF-YF+FX+FX-YF-YFFX+YF+FXFXYF-FX+YF+FXFX+YF-FXYF-YF-FX+FX+YFYF-", 
        "Y": "+FXFX-YF-YF+FX+FXYF+FX-YFYF-FX-YF+FXYFYF-FX-YFFX+FX+YF-YF-FX+FX+YFY"}
iterations = 2 # TOP: 3
angle = 90


摩尔曲线

axiom = "LFL-F-LFL"
rules = {"L":"+RF-LFL-FR+", "R":"-LF+RFR+FL-"}
iterations = 0 # TOP: 8
angle = 90


希尔伯特曲线

axiom = "L"
rules = {"L":"+RF-LFL-FR+", "R":"-LF+RFR+FL-"}
iterations = 8 # TOP: 9
angle = 90


希尔伯特曲线II

axiom = "X"
rules = {"X":"XFYFX+F+YFXFY-F-XFYFX", "Y":"YFXFY-F-XFYFX+F+YFXFY"}
iterations = 4 # TOP: 6
angle = 90


豌豆曲线

axiom = "F"
rules = {"F":"F+F-F-F-F+F+F+F-F"}
iterations = 2 # TOP: 5
angle = 90


交叉

axiom = "F+F+F+F"
rules = {"F":"F+FF++F+F"}
iterations = 3 # TOP: 6
angle = 90


三角形

axiom = "F+F+F"
rules = {"F":"F-F+F"}
iterations = 2 # TOP: 9
angle = 120


龙曲线

axiom = "FX"
rules = {"X":"X+YF+", "Y":"-FX-Y"}
iterations = 8 # TOP: 16
angle = 90


Terdragon曲线

axiom = "F"
rules = {"F":"F-F+F"}
iterations = 5 # TOP: 10
angle = 120


双龙曲线

axiom = "FX+FX"
rules = {"X":"X+YF+", "Y":"-FX-Y"}
iterations = 6 # TOP: 16
angle = 90


三重龙曲线

axiom = "FX+FX+FX"
rules = {"X":"X+YF+", "Y":"-FX-Y"}
iterations = 7 # TOP: 15
angle = 90


代码

以上所有示例都是使用相同的代码获得的,在使用它们时,存在一些困难(例如,如何使分形尽可能保持在中心),使用颜色,反转,偏移量以及快速导出到矢量格式。在这里,我仅向您展示最简单的版本。
此版本以黑白显示分形,并且不具备导出功能

import turtle

def create_l_system(iters, axiom, rules):
    start_string = axiom
    if iters == 0:
        return axiom
    end_string = ""
    for _ in range(iters):
        end_string = "".join(rules[i] if i in rules else i for i in start_string)
        start_string = end_string

    return end_string


def draw_l_system(t, instructions, angle, distance):
    for cmd in instructions:
        if cmd == 'F':
            t.forward(distance)
        elif cmd == '+':
            t.right(angle)
        elif cmd == '-':
            t.left(angle)


def main(iterations, axiom, rules, angle, length=8, size=2, y_offset=0,
        x_offset=0, offset_angle=0, width=450, height=450):

    inst = create_l_system(iterations, axiom, rules)

    t = turtle.Turtle()
    wn = turtle.Screen()
    wn.setup(width, height)

    t.up()
    t.backward(-x_offset)
    t.left(90)
    t.backward(-y_offset)
    t.left(offset_angle)
    t.down()
    t.speed(0)
    t.pensize(size)
    draw_l_system(t, inst, angle, length)
    t.hideturtle()

    wn.exitonclick()


代码说明

import turtle


首先,您需要导入Turtle模块

def create_l_system(iters, axiom, rules):
    start_string = axiom
    if iters == 0:
        return axiom
    end_string = ""
    for _ in range(iters):
        end_string = "".join(rules[i] if i in rules else i for i in start_string)
        start_string = end_string

    return end_string

然后,您需要生成一个L系统,这将是乌龟的一组指令。我们定义了一个函数create_l_system该函数接收迭代次数,公理和构造规则。它以公理开始,并使用辅助变量end_string,如果迭代次数为0,则它​​将返回公理,因为某些分形也可以应用零迭代。在这种情况下,假定规则具有字典的形式,因此每个键都是唯一的,代表一个符号,并且值指示需要替换的内容。因此,我们将每个字符的所有替换组合在一起,并最终为下一次迭代获取一个字符串。

def draw_l_system(t, instructions, angle, distance):
    for cmd in instructions:
        if cmd == 'F':
            t.forward(distance)
        elif cmd == '+':
            t.right(angle)
        elif cmd == '-':
            t.left(angle)

然后,我们确定draw_l_system哪个接受乌龟,一组指令(L系统的输出),左转或右转的角度以及每条线的长度。它由elif每个先前定义的团队的简单结构组成。

def main(iterations, axiom, rules, angle, length=8, size=2, y_offset=0,
        x_offset=0, offset_angle=0, width=450, height=450):

    inst = create_l_system(iterations, axiom, rules)

    t = turtle.Turtle()
    wn = turtle.Screen()
    wn.setup(width, height)

    t.up()
    t.backward(-x_offset)
    t.left(90)
    t.backward(-y_offset)
    t.left(offset_angle)
    t.down()
    t.speed(0)
    t.pensize(size)
    draw_l_system(t, inst, angle, length)
    t.hideturtle()

    wn.exitonclick()

最后,让我们说说功能main,它采取一切必要的L-系统的生成参数,以及y_offsetx_offsetoffset_anglewidthheight前三个描述了乌龟的位移,只需要按照我们想要的方式将图形放置在画布上即可。

该函数首先生成一组指令并将其保存在inst中,然后初始化乌龟和屏幕并将乌龟放置在特定点,然后根据指令绘制图形并等待单击以关闭。

特别注意事项

正如我上面提到的,这里还有许多限制。首先,我们没有为乌龟提供无需渲染即可移动的能力;这将需要另一个字符。也没有用于退回和记住先前位置的符号。它们不是上面讨论的所有分形所必需的,但是其他一些分形(例如,分形树)是必需的。

其他资源

互联网上有很多关于分形的资源,无论是从编程的角度还是从数学的角度来看,分形的资源都被考虑在内。以下两个对我来说似乎特别有趣:3Blue1Brown(数学)和CodingTrain(代码)。

这篇文章是由灵感从数学世界和文章 宝拉·伯卡(Paula Burka)。

All Articles