即使在静态图片中,一些吸引子也以其美丽而着迷。我们希望开发出一种能够以动态方式可视化大多数吸引子的应用程序,并具有3D滞后性。
关于我们
我们-Roman Venediktov,Vladislav Nosivskoy和Kirill Karnaukhov-是圣彼得堡高等经济学院“应用数学和信息学”学士学位课程的二年级学生。自上学以来,我们一直喜欢编程。他们三人都参加了奥林匹克编程,并在不同的年份进入了面向计算机科学领域的全俄奥林匹克竞赛的最后阶段,但是他们之前没有工业编程的经验,对我们来说这是第一个大型团队项目。我们捍卫了它作为C ++的学期论文。
造型
定义具有奇异吸引子的动力学系统的方法有很多,但是最常见的是由三个一阶微分方程组成的系统。我们从她开始。
在可视化某些东西之前,您需要模拟过程本身并找到这些点的轨迹。准确的建模方法非常费力,我们希望尽快进行。
在实现建模时,我们决定使用元编程,放弃std :: function和其他类似机制。他们本来可以简化架构和编码,但是会大大降低性能,这是我们所不希望的。
最初,使用最简单的四阶精度Runge-Kutta方法(步长恒定)进行建模。到目前为止,我们还没有增加模型的方法和其他数学组成部分的数量,现在这是唯一提出的方法。在发现的大多数系统上,它足以产生与来自其他来源的图像相似的图像。
该模型接受输入:
- 通过点的坐标获取导数的“导数”函子;
- “观察者”函子,从接收到点即被调用;
- 模拟参数(起点,步长,点数)。
将来,您可以添加检查以查看所显示图片与真实图片的匹配程度,一些更强大的建模方法(例如,通过连接Boost.Numeric.Odeint库)以及一些我们对数学的了解还不够的其他分析方法。
系统篇
我们找到了最受欢迎的奇怪吸引子系统,以从中获得最佳性能。在这里,我们要对网站chaoticatmospheres.com表示感谢,它使我们的搜索非常容易。
所有系统都必须包装好,以便尽管它们都是“我们的模板”,但可以将它们放入容器中并在控制器中正常使用它们。我们得出以下解决方案:
- DynamicSystem ‘observer’, (, ...) std::function ‘compute’. ‘Compute’ , , ‘derivatives’ .
- std::function , DynamicSystemInternal compute .
- DynamicSystemInternal ‘observer’, ‘derivatives’. ‘derivatives’, .
我们开始添加DynamicSystemWrapper的工作,该DynamicSystemWrapper拥有DynamicSystem,并且可以进行可视化所需的预处理(选择常数进行归一化,对于具有步长控制的方法可接受的误差...),但是没有时间来完成。
可视化
我们选择OpenGL作为渲染库是因为它的性能和功能,还有Qt5,它具有对OpenGL的方便包装。
首先,我们想学习如何绘制至少一些东西,一段时间后我们就能制作出第一个立方体。此后不久,出现了数学模型的简单版本,这是吸引子的首次可视化:
在渲染的第一个版本中,还准备了一个非常简单的相机版本。她知道如何绕一个点旋转并接近/移开。我们希望有更多的空间自由:吸引子是不同的,需要以不同的方式进行探索。然后出现了第二个版本的相机,它可以在各个方向上自由旋转和移动(在Minecraft中,我们由相机引导)。那时,线性代数才刚刚开始,因此知识不足:我们不得不在Internet上寻找大量信息。
一直以来,这些图片都是白色的,静止的,无趣的。我想添加颜色和动态效果。首先,我们学习了如何以一种颜色绘制整个图片,但是事实证明这也没什么意思。然后,我们提出了以下解决方案:
- 彼此接近的起点很多(100–500,您可以在设置中选择更多,主要是因为有足够的性能)。
- 模拟它们各自的轨迹。
- 同时渲染路径,同时以不同的颜色为其着色,并且仅显示路径的一部分。
结果是:
大约直到最后这个方案仍然存在。
令我们震惊的是,线条太“角”了,我们决定学习如何平滑它们。当然,我们尝试减少了仿真步骤,但是不幸的是,即使是现代处理器也无法计算出这么多点。有必要寻找另一种选择。
最初,我们认为OpenGL应该具有线条平滑工具,但是经过大量搜索,我们发现并非如此。然后,提出了对曲线进行插值并在相距足够远的每对相邻点之间添加更多点的想法。为此,必须选择一种插值曲线的方法,并且有很多这样的方法。 las,其中大多数(例如,贝塞尔曲线)需要指定更多点,这显然不适合我们的任务:我们希望结果仅取决于数学模型提供给我们的内容。一段时间后,我们找到了合适的插值方法:Catmull-Roma曲线。原来是这样的:
之后,我们决定最好在应用程序内录制视频。我们希望保持跨平台,因此我们选择了libav库(在库之间几乎没有选择)。不幸的是,整个库都是用C编写的,并且具有非常不方便的界面,因此花了很长时间学习了如何编写东西。所有后续gif都是使用内置录音制作的。
至此,所有曲线颜色都已在创建时明确指定。我们认为,要拍出漂亮的照片,我们需要设置不同的颜色。为此,仅开始显示对照色,其余使用线性渐变进行计算。此部分已移至着色器(在成为标准着色器之前)。
我们发现有趣的是,对轨迹进行着色以使每个轨迹都从头到尾改变颜色。这使我们能够观察到速度的影响:
然后,我们认为值得尝试减少轨迹的预处理时间:对曲线进行插值是“昂贵的”操作。决定将此部分转移到着色器,以便GPU在每次要求绘制轨迹的一部分时计算插值。为此,我们使用了几何着色器。该解决方案具有许多优点:渲染之前在渲染方面没有延迟,能够平滑曲线(这种计算在GPU上比在CPU上执行得更快),使用的RAM更少(在此之前,必须存储所有内插点,现在-否) )。
控制器和用户界面
在选择Qt5作为基本框架之后,选择接口技术的问题立即消失了。内置的Qt Creator足以满足小型应用程序的所有需求。
为了响应用户请求,必须编写一个控制器。幸运的是,Qt具有便捷的方式来处理击键并将值输入字段。它使用Qt的主要思想-信号和时隙机制。例如,如果在我们的应用程序中按下负责重建模型的按钮,则会生成一个信号,该信号将被处理程序插槽接受。它将自行开始重建。
当开发几乎所有带有接口的应用程序时,迟早都会出现使应用程序成为多线程的想法。在我们看来,这是必要的:构建内置模型需要花费几秒钟,而构建自定义模型则需要10秒。当然,同时,该接口挂起了,因为所有计算都在一个线程中进行。很长时间以来,我们讨论了不同的选项,并考虑了使用std :: async进行异步处理,但最终我们意识到我们希望能够中断另一个线程中的计算。为此,我必须在std ::线程上编写一个包装器。一切都尽可能地简单:用于检查的原子标志和在检查失败时进行的整洁中断。
这不仅提供了理想的结果-界面停止挂起-而且还添加了一些功能:由于架构的特殊性以及模型数据与可视化之间的交互作用,可以在计数过程中在线绘制所有内容。以前,您必须等待所有数据。
定制系统
该应用程序中已经介绍了许多吸引子,但我们也希望允许用户自己输入方程式。为此,我们编写了一个解析器,该解析器支持变量(x,y,z),标准数学运算(+-* / ^),常量,许多数学函数(sin,cos,log,atan,sinh,exp等)。和括号。它是这样工作的:
- 原始查询字符串已标记化。接下来,从左到右解析标记,并构建一个表达式树。
- 可能的操作分为几组。每个组都有自己的节点。组:正负,乘除,指数,一元负,所谓的工作表(包括常数,变量,函数调用)。
- 每个组都有自己的计算级别。每个级别都会在下一个级别进行递归计算。您可以看到调用顺序会影响操作优先级的分配。我们按上述顺序购买它们。
在解析器源代码中查找更多详细信息。
每个级别返回Node的某种继承者。其中有四个:
- 二进制运算符-存储指向两个子代及其自身类型的操作的指针;
- 一元运算符-存储指向子项及其自身操作类型的指针。这包括功能,因为这是一元运算的特殊情况;
- 常量-存储其值;
- variable-将指针存储在其值所在的内存中的位置。
Node结构仅具有一个虚拟calc函数,该函数返回其子树的值。
生成的输出非常方便地适合于先前描述的系统体系结构。简单地将一个lambda传递给DynamicSystemInternal,该函数将存储指向获得的三棵树的根节点和值的xyz内存位置的指针。调用时,它将值更改为提供的值,并从根顶点调用calc。
结果
结果,我们得到了一个程序,该程序可以可视化用户定义的系统,并且具有大量吸引子。她做得很好而且很优化,这是个好消息。
但是仍然有很多工作:
- 添加更精确的方法;
- 增加一层系统处理(以更复杂的方法进行规范化和自动错误选择);
- 改善用户系统的工作(支持变量,保存);
- 优化他们的工作(JIT编译或将保存的系统转换为c ++代码并直接开始重新编译以使其达到嵌入式系统性能的实用程序);
- 添加使用此类系统的人员真正需要的结果分析或可视化功能;
- ...
我们的资料库。
还有一些带有吸引子的视频: