使用OpenGL进行Mandelbrot集的可视化
Mandelbrot集是哪一集??
Mandelbrot集不是哪一集!! 啊不对……
Mandelbrot集是哪一集!! 好像也不对……
Mandelbrot集是数集!! 所以……他不是一集而是数集??……
所以这个M...dem...集到底是什么啊??
Mandelbrot集是一个数集
Mandelbrot集\(\mathbb{M}\)(简称曼集)是一个由二元复数构成的集合,也就是一个复数集:
\]
也就是说,曼集的元素都是复数,也就是如下的形式:
\]
为什么叫这么复杂的名字??
本华·曼德勃罗特(Benoit B.Mandelbrot)是分形学(Fractals) 的鼻祖,是的,Fractals这个词就是他提出的。
不过,这个集合本身并不是他本人提出的,而是一个叫做阿德里安·杜阿迪的法国数学家提出的,至于为什么以曼德勃罗特命名,根据wiki的说法是提出者为了向曼德勃罗特致敬而放弃用自己的名字命名。
这是个什么样子的集合??
一个复数\(z\)在或不在曼集中,取决于如下数列是否收敛:
\]
\]
其中,\(f(z;c)=z^2+c\),\(c\)是一个复参数。
如果把\(f\circ^n(z;c)\)展开写的话就是:
\zeta_n&=f\circ^n(z;c)=z_n^2+nc \\
z_n&=z_{n-1}^2+c
\end{split}
\tag{2}
\]
由于在其中迭代的前一个\(z\)都是以平方的形式出现,这也就意味着
\left|\Im\left(z_n\right)\right|\ge\left|\Im\left(z_{n-1}\right)\right|\ge 0
\]
这样一来,若\(z_n\)收敛,则\(z_m(m<n)\)必然收敛。
综上描述,有下结论:
\]
式3即曼集判定的充要条件或定义。
为了让问题变得简单,这里可以令初始值\(z=0\),这样式2变成了仅与参数\(c\)相关的形式:
z_0 &=0 \\
z_1 &=c \\
z_2 &= z_1^2+c = c^2+c \\
z_3 &= z_2^2+c = c^4+2c^3+c^2+c \\
&\cdots \\
\end{split}
\]
复数??迭代??还收敛??听起来好像很复杂……
这个……是的,这些东西听起来很不直观,以至于我也花了好久看了大量资料才搞清楚是什么意思。
但是,这件事本身的中心思想并不复杂,其无非做了这样的一件事情:
- 给定一个复数\(c\)
- 计算\(z_+=z^2+c\)(\(z_+\)会成为下一次的\(z\))
- 就这样,反复执行2,直到山穷水尽之时
- \(z_+\)仍然没有飞升为\(\infty\),那\(c\in\mathbb{M}\)
- 否则存在某一次\(z_+=\infty\)则\(c\notin\mathbb{M}\)
山穷水尽之时??那我恐怕等不到那一天了……
不必沮丧,我们甚至没打算去推导出\(z_n\)的完整展开形式,而且,计算机也无法处理需要运行无数次才能解决的问题(停机问题)。
但是,在有限的,可预见的次数(迭代数)内,进行这样的判定还是可行的并且能得出结果的。
但是这样的话肯定会有纰漏吧
说的对,因为只要迭代数\(n\)有限,\(z_n\)是总能求出确定值。但正因为如此,迭代数的增长才会产生实际的意义。这就好像,没有人能求出\(\pi\)的所有小数位数,但是每当有人多求出正确的一位的时候,其结果与实际值就总会更加接近。也就是说,只要范围有限,我们求得的曼集就总比实际的曼集大,但随着次数的增多,结果集合会向曼集逐渐地靠近。
所幸,在有限的次数内,我们尽管不能立即断言哪些数一定属于曼集,但我们能断言哪些数一定不属于曼集,那正是:
还没上高速公路就要起飞的,那必然是要玩完的
在有限次计算就体现出明显的发散趋势的时候,那基本也就抢救无效了
好吧好吧,那有哪些值是可以放弃治疗的?
根据迭代函数的定义,\(f(z;c)=z^2+c\),我们对函数的范数(也就是模,或者称为复数的绝对值)进行考察,至少如果\(f\)收敛,其模与带入z的模至少是相等的(当然,原则上幅角也应当相等),:
|z|\ge0
\]
根据绝对值三角不等式(这个对复数域同样成立,实际上就是向量绝对值三角不等式的移植),若\(|z|=0\)则\(|c|\)必然为\(0\),否则
1-|c|\le|z|\le1+|c|
\]
由于迭代初始条件\(z_0=0\),因此经过n次迭代后的\(z\)一定是
c\le|z_n|\le\sum_{m=0}^{(n-2)^2}{|k_m||c^{m}|}
\]
上面的不等数右侧产生了一个很熟悉的形式——幂级数,如果要幂级数\(\sum_{m=0}^{(n-2)^2}{|k_m||c^{m}|}\)收敛,则\(|c|<1\)是必要条件(由于\(k_m\)的大小这里没有多加考虑,即使\(|c|<1\)满足,发散的可能同样存在,这个在后续的迭代过程中会进一步的被发掘出来),否则根据幂级数的性质级数必然发散,这样得到了一个必要阈值:\(|c_\Theta|=1\),将这个阈值带入,我们就能得到\(|z|\)的基础发散判定值:
\]
在后面的程序中,我们将使用这个值作为发散判定,意思就是说,如果算出\(|z|\ge 2\)了,那就不用考虑了,你不可能收敛的,至于剩下的值,我只假设但不断言它一定在集合内,因此我暂且将它认为是集合内的,如果经过更多的计算发现也达到这个红线条件了,那么同样不论这个值挣扎了多久,OUT!
但是,复数的收敛并不限于模收敛,当复数被整理为辐角形式\(r\angle\theta\)时,若\(\theta\)没有稳定值即便\(r\)已经收敛仍然不能断言复数收敛(毕竟啊,那个复平面上的向量如果一直在原地打转,分量就总在以一个有界值变化,宛如论证的丧钟在转动),但是对于这个函数而言根本不会不存在这种情形!
哇,断言的这么轻松??何出此言??
其实很容易,找到这样的向量,利用迭代函数平方相加后保持模不变即可,所幸的是,这样的值非常有限,因为首先一个条件就将范围急剧缩减:
\]
是的,只有\(|c|=1\)的时候,平方计算后才可能保证模不变(依据辐角计算法若\(z=r_z\angle\theta\),则\(z^2=r_z^2\angle{2\theta}\)),当然我是指仅一次,因为还另外需要一件事保证\(z^2+c=c^2+c\)后的模长也是\(|c|\)或者说是\(1\):
\]
也就是说\(\arg{z^2}-\arg{c}=\frac{2k\pi}{3}\)才可以(这个式子写的不是很严谨,但你当成是两个夹角为\(120^\circ\)就好),当然你会反驳:
\(c\)是随意取得,也就是说幅角就可以是任意值,当然,\(z^2\)的取值决定于\(c\),也就是\(z^2\)可以和\(c\)构造连续的函数映射,那么你根本保证不了\(z^2\)一定能够避开这些值啊你个代数白痴!!
啊是的,但是你很快就会意识到一个事实:
这个丧钟,转不久的!!
什么??
确实,当满足上述两个条件之后,得到的\(z_+\)的结果模仍然是\(1\),只是转了个角度,但是架不住这个函数是迭代的啊,这次得到的\(z_+\)很明显位于这\(z^2\)和\(c\)的角平分线处……
然后,夹角条件被打破了!!
就像雪崩一样,这个微妙的平衡很快就被打破了,并且马上开始急剧的发散(或收敛)。
如丸走坂
- Ramp Rollerball
这样一来,通过多次的迭代运算,不合格的点一点一点被筛出,如果次数足够多,这个集合最后形成的图形被称为Mandelbrot分形。
大概长出来是这样的:
这个M..d什么b什么的分形有什么特点么??
作为一个分形,它最大的特点之一就是:
德罗斯特效应(自相似性,Droste Effect)
如果对这个图形局部放大,你会看到这个与最开始看到一致的子图性,而且:
放大一次有,一直放大一直有!放大放大再放大!每一个子部都看的清清楚楚!
德罗斯特效应本质是递归式的图形体现,因为迭代函数通过\(z_+=f(z;c)\)进行了递推,这样的话,也就意味着,分形具有无限细微的结构,而且跟整体是相似的。
而具有无限精细的结构也就意味着:它具有有限的面积,而周长却很可能无穷大
当然由于曼集的边缘性质非常复杂,这里就不再详细讨论他的周长是否收敛了(面积收敛是一看就能看出来的),毕竟这篇文章是为了说明如何通过OpenGL实现这个集合的可视化,上面其实已经花了大量的篇幅去推导一个阈值了。
Mandelbrot集的兄弟——Julia集简介
之前是以\(c\)作为参数进行迭代,得到了曼集,那么,如果把\(c\)和\(z\)交换一下,让\(z\)做参数,就得到了Mandelbrot集的兄弟——Julia集(以下简称朱集),以法国数学家加斯顿·朱利亚(Gaston Julia)命名。
— Oh,Julia~
— Oh,Mandelbrot~
— W...What??
同样,因为递归仍然存在,Julia集得到的也会是Julia分形:
迭代函数没变,因此它的发散判定也是和曼集是一致的,这里仅仅介绍一下这个亲戚,并不打算实现这个Julia集的可视化。
OpenGL可视化实现
其实上面的函数都已经推导完毕的情况下,并且也知道集合是如何生成的之后,代码实现就容易多了,绘制曼集的代码如下(只考虑整数点):
基本绘制(二值化)
void DrawMandelbrot(int maxIter)
{
complex<double> z(0,0); // 位于标准库<complex>头文件中
complex<double> c;
complex<double> f(0,0);
bool diverge = false;
glBegin(GL_POINTS);
for (GLint x = -400; x < 400; x++)
{
diverge = false;
for (GLint y = -300; y < 300; y++)
{
c = { static_cast<double>(x)/150,static_cast<double>(y)/150 };
int iter;
for (iter = 0; iter < maxIter; iter++)
{
z = z*z + c;
if ( abs(z) >= 2 ) //发散判定
{
diverge = true;
break;
}
}
if (!diverge)
{
glColor3f(0.0f, 0.0f, 0.0f); //集合内黑色
}
else
{
glColor3f(1.0f, 1.0f, 1.0f); //集合外白色
diverge = false;
}
z = { 0,0 };
glVertex2i(x, y);
}
}
glEnd();
}
效果图:
有问题,如果发散判定值不取2会有什么影响??
问得好,实际上我正想说,其实判定值最好取2,如果实在无法取得的话,也至少请保证它大于2
如果小于2,则有些本来应该在曼集内的点被剔除,这样形成的曼集图形是不完整的。
而如果大于2,当迭代次数非常大的时候,实际上得到的结果与2的时候相差不大(细微的内部可能会有所差别),因为次数非常大的时候,该发散的值一般早都散得没影了,用再大的有限值根本拦不住。但是如果迭代次数非常小的时候,得到的曼集会包含大量冗余的点,因为阈值没能称职地起到约束作用。
迭代着色
当然,无论如何,我们可以让上面的图更加漂亮一些,比如我们以迭代多少次就发散了作为着色的依据,这里对上面的图形进行着色处理。
实际上只需要把集合外的着色代码里面的颜色分量改成与迭代数iter
相关的形式:
//...
else
{
glColor3f(static_cast<float>(iter) / maxIter, (0.5f*iter) / maxIter, 0.15f*iter / maxIter);
diverge = false;
}
绘制结果:
值得注意的是,由于迭代次数是一个离散的值,因此整个图片的着色显得并不是那般的丝滑和连续,在其他文章中还有提到过对数化将使着色更加柔和漂亮的方法,这里不再阐述。
当指定参数maxIter
的次数不同,形成的图形也不同,并且,随着迭代次数的增大, 形状逐渐趋于稳定,更接近于标准的曼集图样:
使用OpenGL进行Mandelbrot集的可视化的更多相关文章
- 广义mandelbrot集,使用python的matplotlib绘制,支持放大缩小
迭代公式的指数,使用的1+5j,这是个复数.所以是广义mandelbrot集,大家能够自行改动指数,得到其它图形.各种库安装不全的,自行想办法,能够在这个站点找到差点儿全部的python库 http: ...
- docker学习------centos7.5下的swarm集群可视化构建
1.swarm集群 manager : 192.168.211.175 agent1 : 192.168.211.176 agent2 : 192.168.211.177 2.环境 ...
- kube-liveboard: kubernetes集群可视化工具
kube-liveboard 随着kubernetes 集群的增大,对于集群数据选取恰当的形式进行展示有助于直观反映集群的状态,方便发现集群的短板,了解集群的瓶颈.因此,笔者做了kube-livebo ...
- 【C++】Mandelbrot集绘制(生成ppm文件)
曼德勃罗特集是人类有史以来做出的最奇异,最瑰丽的几何图形.曾被称为"上帝的指纹". 这个点集均出自公式:Zn+1=(Zn)^2+C.(此处Z.C均为复数)所有使得该公式无限迭代后的 ...
- Docker Swarm(十)Portainer 集群可视化管理
前言 搭建好我们的容器编排集群,那我们总不能日常的时候也在命令行进行操作,所以我们需要使用到一些可视化的工具,Docker图形化管理提供了很多工具,有Portainer.Docker UI.Shipy ...
- VB6之Mandelbrot集
Mandelbrot真是上帝之作,数学之美最直观的表现. 围观wiki和百科(百度百科)上关于Mandelbrot的解释至今仍是不能理解,没办法我高数实在学得不好. 搜素到园友用F#写的一篇实现代码, ...
- XNginx - nginx 集群可视化管理工具
之前团队的nginx管理,都是运维同学每次去修改配置文件,然后重启,非常不方便,一直想找一个可以方便管理nginx集群的工具,翻遍web,未寻到可用之物,于是自己设计开发了一个. 效果预览 集群gro ...
- opengl纹理映射总结
大概步骤: 1.创建纹理对象,并为他指定一个纹理. 2.确定纹理如何应用到每个像素上. 3.启用纹理贴图 4.绘制场景,提供纹理和几何坐标 过滤:由于我们提供的纹理图像很少能和最终的屏幕坐标形成对应, ...
- Draw_extend使用OpenGL显示数据点
//alter load_map.dev //safety verion 2016/1/12 #include <iostream> #include <fstream> #i ...
随机推荐
- CODEVS【1025】选菜
1025 选菜 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 在小松宿舍楼下的不远处,有PK大学最不错的一个食堂——The ...
- BZOJ【1625】宝石手镯
1625: [Usaco2007 Dec]宝石手镯 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1007 Solved: 684[Submit][St ...
- C#可选参数与具名参数
可选参数 static void test1() { func1("A"); func1(); Console.ReadKey(); } ) { Console.WriteLine ...
- 自动从网站上面下载文件 .NET把网站图片保存到本地
原文发布时间为:2009-11-19 -- 来源于本人的百度文章 [由搬家工具导入] 自动从网站上面下载文件 .NET把网站图片保存到本地简单范例:这个范例是把百度联盟这个logo保存到根目录下面,当 ...
- LeetCode总结【转】
转自:http://blog.csdn.net/lanxu_yy/article/details/17848219 版权声明:本文为博主原创文章,未经博主允许不得转载. 最近完成了www.leetco ...
- linux内核情景分析之强制性调度
从系统调用返回到用户空间是否调度,从ret_with_reschedule可看出,是否真正调度,取决于当前进程的pcb中的need_resched是否设置为1,那如何设置为1取决于以下几种情况: 时间 ...
- 记Django数据库迁移过程中遇到的一些问题
首先描述一下问题,Django 数据库使用的mysql, 然后开始没注意,没建一个default库,就把第一个数据库当成默认的了,结果Django的admin相关的那些表,都自动生成到这个库里了,现在 ...
- LeetCode OJ-- Substring with Concatenation of All Words ***
https://oj.leetcode.com/problems/substring-with-concatenation-of-all-words/ 找S中子串,每个元素都在T中出现了,且所有T中元 ...
- visual studio 插件 resharper 使用指南
vs虽然号称是宇宙第一ide,但在智能提示和代码分析方面还是要略逊于jetbrains系列的ide.如果将jetbrains系列ide的智能提示和代码分析集成到vs中,对vs来说无异于如虎添翼.res ...
- 2017 ACM-ICPC EC-Final 记录
北京赛区结束后就以为自己的赛季结束了……但是还是保持着做题量 那天突然接到通知,去打EC-Final 但是这是一个临时组起来的队伍,另外两位队友原来一起组的比较熟,我就需要适应一下. 于是我们临时训练 ...