二十余年前读本科时,一日周末在书店看到一本分形图形学,被其中玄妙的图案及炫酷的数学公式表达所吸引,顿觉眼界忽开,欣喜异常。尽管当时不甚明白其中奥义,且平日已购书花费颇多,也丝毫没有犹豫,将其买回。后时时翻阅此书,苦于资质有限,不能理解其中玄妙,仅仅获得一些概念性的认知,哦,原来自然界的很多现象,可以用分形,可以用数学描述,用计算机进行模拟生成。时至二十多年之后,似乎开始理解其中真意,其原与道相通,亦或用阴阳可释之。 混沌,分形,突变,涌现,自相似,自组织等等复杂性研究的概念或相关领域自上世纪80年代圣塔菲研究所成立以来即逐渐从边缘走向学术研究的重要领域,钱学森老前辈提出的复杂巨系统等等,都逐渐成为成为西方主导的显学研究的重要内容,其中不仅仅涉及数学、物理、化学、信息技术等基础自然科技领域,更包含人文、艺术等等诸多的范畴,成为跨学科研究的基础学科方向。关于复杂性研究及发展的综述文章甚多,文末列出一些可供参考,这里不做赘述,仅就自己的研究体会与君交流分享。 1890年,意大利数学家皮亚诺(Piano)构造了一种奇怪的曲线,该曲线自身并不相交,但是它却能遍历一个正方形内部所有的点,得到一条充满空间的曲线。换句话说,这条曲线实际就是正方形本身,它拥有和正方形一样的面积,这个结论似乎令人不可思议。其实非常符合道家阴阳的思想,有无相生,一气流转的逻辑,甚至同伦与禹步、内家拳等锻炼步法。 在皮亚诺论文发表后的第二年,希尔伯特在同一期刊上发表了皮亚诺曲线另一种形式的构造,并配以插图说明来帮助理解,也被成为希尔伯特曲线,该方法非常容易由计算机算法生成,网络上也能看到非常多这样的生成代码和视频介绍。 希尔伯特曲线是以下一系列分形曲线Hn的极限,我们可以把 Hn看作一条覆盖2n × 2n方格矩阵的曲线,曲线上一共有 2n × 2n 个顶点(包括左下角起点和右下角终点),恰好覆盖每个方格一次。 n为0时,正方形无限缩小到0维是一个点,这时铺满这个点只需要零维的点,这时可以认为皮亚诺曲线的太极点。 n为1时算法初定分形结构,乾1为入,过离2经坎3,至坤4,完成1阶希尔伯特曲线构造,即由太极定阴阳生四象H1。 当n > 1时,Hn可以通过如下方法构造生成: 1. 将Hn-1顺时针旋转90度放在左下角 2. 将Hn-1逆时针旋转90度放在右下角 3. 将2个Hn-1分别放在左上角和右上角 4. 用3条单位线段把4部分连接起来 算法中低阶分形的初始旋转非常重要,具体表现在两个不同分形的阴阳二气交界处都是乾坤相连,入口为乾,出口为坤,如此反复循环迭代。 该算法的各种代码实现可见参考文献[6]。 '''Hilbert curve''' from itertools import (chain, islice) # hilbertCurve :: Int -> SVG String def hilbertCurve(n): '''An SVG string representing a Hilbert curve of degree n. ''' w = 1024 return svgFromPoints(w)( hilbertPoints(w)( hilbertTree(n) ) ) # hilbertTree :: Int -> Tree Char def hilbertTree(n): '''Nth application of a rule to a seedling tree.''' # rule :: Dict Char [Char] rule = { 'a': ['d', 'a', 'a', 'b'], 'b': ['c', 'b', 'b', 'a'], 'c': ['b', 'c', 'c', 'd'], 'd': ['a', 'd', 'd', 'c'] } # go :: Tree Char -> Tree Char def go(tree): c = tree['root'] xs = tree['nest'] return Node(c)( map(go, xs) if xs else map( flip(Node)([]), rule[c] ) ) seed = Node('a')([]) return list(islice( iterate(go)(seed), n ))[-1] if 0 < n else seed # hilbertPoints :: Int -> Tree Char -> [(Int, Int)] def hilbertPoints(w): '''Serialization of a tree to a list of points bounded by a square of side w. ''' # vectors :: Dict Char [(Int, Int)] vectors = { 'a': [(-1, 1), (-1, -1), (1, -1), (1, 1)], 'b': [(1, -1), (-1, -1), (-1, 1), (1, 1)], 'c': [(1, -1), (1, 1), (-1, 1), (-1, -1)], 'd': [(-1, 1), (1, 1), (1, -1), (-1, -1)] } # points :: Int -> ((Int, Int), Tree Char) -> [(Int, Int)] def points(d): '''Size -> Centre of a Hilbert subtree -> All subtree points ''' def go(xy, tree): r = d // 2 def deltas(v): return ( xy[0] + (r * v[0]), xy[1] + (r * v[1]) ) centres = map(deltas, vectors[tree['root']]) return chain.from_iterable( map(points(r), centres, tree['nest']) ) if tree['nest'] else centres return go d = w // 2 return lambda tree: list(points(d)((d, d), tree)) # svgFromPoints :: Int -> [(Int, Int)] -> SVG String def svgFromPoints(w): '''Width of square canvas -> Point list -> SVG string''' def go(xys): def points(xy): return str(xy[0]) + ' ' + str(xy[1]) xs = ' '.join(map(points, xys)) return '\n'.join( ['<svg xmlns='http://www./2000/svg'', f'width='512' height='512' viewBox='5 5 {w} {w}'>', f'<path d='M{xs}' ', 'stroke-width='2' stroke='red' fill='transparent'/>', '</svg>' ] ) return go # ------------------------- TEST -------------------------- def main(): '''Testing generation of the SVG for a Hilbert curve''' print( hilbertCurve(6) ) # ------------------- GENERIC FUNCTIONS ------------------- # Node :: a -> [Tree a] -> Tree a def Node(v): '''Contructor for a Tree node which connects a value of some kind to a list of zero or more child trees.''' return lambda xs: {'type': 'Node', 'root': v, 'nest': xs} # flip :: (a -> b -> c) -> b -> a -> c def flip(f): '''The (curried or uncurried) function f with its arguments reversed. ''' return lambda a: lambda b: f(b)(a) # iterate :: (a -> a) -> a -> Gen [a] def iterate(f): '''An infinite list of repeated applications of f to x. ''' def go(x): v = x while True: yield v v = f(v) return go # TEST --------------------------------------------------- if __name__ == '__main__': main() import matplotlib.pyplot as plt import numpy as np import turtle as tt # dictionary containing the first order hilbert curves base_shape = {'u': [np.array([0, 1]), np.array([1, 0]), np.array([0, -1])], 'd': [np.array([0, -1]), np.array([-1, 0]), np.array([0, 1])], 'r': [np.array([1, 0]), np.array([0, 1]), np.array([-1, 0])], 'l': [np.array([-1, 0]), np.array([0, -1]), np.array([1, 0])]} def hilbert_curve(order, orientation): ''' Recursively creates the structure for a hilbert curve of given order ''' if order > 1: if orientation == 'u': return hilbert_curve(order - 1, 'r') + [np.array([0, 1])] + \ hilbert_curve(order - 1, 'u') + [np.array([1, 0])] + \ hilbert_curve(order - 1, 'u') + [np.array([0, -1])] + \ hilbert_curve(order - 1, 'l') elif orientation == 'd': return hilbert_curve(order - 1, 'l') + [np.array([0, -1])] + \ hilbert_curve(order - 1, 'd') + [np.array([-1, 0])] + \ hilbert_curve(order - 1, 'd') + [np.array([0, 1])] + \ hilbert_curve(order - 1, 'r') elif orientation == 'r': return hilbert_curve(order - 1, 'u') + [np.array([1, 0])] + \ hilbert_curve(order - 1, 'r') + [np.array([0, 1])] + \ hilbert_curve(order - 1, 'r') + [np.array([-1, 0])] + \ hilbert_curve(order - 1, 'd') else: return hilbert_curve(order - 1, 'd') + [np.array([-1, 0])] + \ hilbert_curve(order - 1, 'l') + [np.array([0, -1])] + \ hilbert_curve(order - 1, 'l') + [np.array([1, 0])] + \ hilbert_curve(order - 1, 'u') else: return base_shape[orientation] # test the functions if __name__ == '__main__': order = 8 curve = hilbert_curve(order, 'u') curve = np.array(curve) * 4 cumulative_curve = np.array([np.sum(curve[:i], 0) for i in range(len(curve)+1)]) # plot curve using plt plt.plot(cumulative_curve[:, 0], cumulative_curve[:, 1]) # draw curve using turtle graphics tt.setup(1920, 1000) tt.pu() tt.goto(-950, -490) tt.pd() tt.speed(0) for item in curve: tt.goto(tt.pos()[0] + item[0], tt.pos()[1] + item[1]) tt.done() 小结如下: 1.基于问题空间结构的分析,合理构造1阶分形结构,分阴阳得四象五行结构,再由1阶分形结构作为太极,按照算法生成高阶次的分形太极结构; 2.算法是驱动分形结构运行的动力核心,依照一气流转的原则可遍历整个二维平面,如若执行规则出现扰动,必然出现无法遍历整个空间的结果; 3.分形结构运行一旦运行趋于无穷时,将会填满整个空间,完成从零至无穷,再归零的过程。 另外一种3×3正方形网格分形的走法如图所示,左边为一阶分形,右边为二阶分形。 这样的九宫的划分方式,皮亚诺曲线的分形走法,是不是很像禹步呢? 的确,看到这里,笔者发现皮亚诺曲线最为玄妙的一气流转之法与道家禹步,内家拳之八卦掌有异曲同工之妙。 正立,右足在前,左足在后,次复前右足,以左足从右足并,是一步也。次复前右足,次前左足,以右足从左足并,是二步也。次复前右足,以左足从右足并,是三步也。如此,禹步之道毕矣。凡作天下百术。皆宜知禹步。 前举左。右过左,左就右。次举右,左过右,右就左。次举右,右过左,左就右。如此三步,当满二丈一尺,后有九迹。 禹步的“九”代表了“九州”即中国:雍、梁、兖、扬、青、荆、徐、豫、冀,按八卦图走上几圈,便代表了其神思已巡历了整个中国(曲线遍历整个正方形)。禹步的“九步”每一步各有其象征意义。《无上玄元三天玉堂大法》卷十九说:“一步像太极,二步像两仪,三步像三才,四步像四时,五步像五行,六步像六律,七步像七星,八步像八卦,九步像九灵”。 南楼先生所教授的转天尊步法亦是源于这上古禹步,有与皮亚诺曲线的空间走法有相通之意。故此,内家拳若按照希尔伯特的算法(分形、旋转、阴阳相连)来走,其亦具有接通阴阳、消灾除魔和驱邪治病的功效,还暗合了强身健体的调神功用;另外,皮亚诺曲线之极最后能填满二维空间,其心法亦与禹步相同。是故曰:参赞天地之化育而与天地融合为一,以达“极”之真意[7,8]。 希尔伯特的皮亚诺曲线构造算法利用自相似的分形结构,非常灵动地展现了一维连续曲线如何在二维平面上遍历穷尽所有点的乾坤拧转、一气流转之法,这是一个从一维空间映射升级到二维的动力系统,由于它能填满平面,其分形豪斯多夫维是2。 利用这个算法,还可以把一维波动的音频和二维的绘画关联起来,利用一定的映射关系,可以使得不同色彩的像素对应特定频率的声音,让绘画发出声音来。实际上,希尔伯特曲线构造方法,在工业领域也有非常多的应用,采用升维的思想,将各种采样得到的一维频谱转换成二维的可视化图像,再利用深度学习等图像处理方法建模,进行模式识别的各种应用。 另外,希尔伯特的皮亚诺曲线构造方法,本质上与道家调神导引之法相通,借用代数拓扑的概念来说,这两者之间是同伦的。 这篇短文中,我们交流了一维曲线的一气流转,那么问题就来了,二维空间是什么神奇的结构可以体现阴阳一气流转的结构呢?三维呢?高维呢?本次先聊到这里,因为有些问题笔者得再花时间来思考,特别是通过近一年的内经导引术的学习过程中,身体亦发生诸多变化,能够逐渐感知过去所学的知识背后的道家阴阳思想相关联,越来越感觉,老祖宗的智慧无法比拟,只是后人无能,无法继承。有缘得先生指路,甚幸甚幸。 待下次我们介绍一些其他好玩的分形结构。 参考文献: 1.伊.普利高津, 伊.斯唐热. 从混沌到有序. 曾庆宏, 沈小峰译. 上海译文出版社,1987. 2.冯.贝塔朗菲. 一般系统论. 林康义等译. 清华大学出版社, 1987.2 3.詹姆斯.格雷克. 混沌:开创一门新科学. 人民邮电出版社2021.9 4.https://cloud.tencent.com/developer/article/1494903 5.江南. 分形几何的早期历史研究. 西北大学博士学位论文. 2018.6 6.https:///wiki/Hilbert_curve 7.马焰瑾, 李锦江, 张其成. 禹步调神健身辨析[J]. 中华中医药杂志(原中国医药学报) 2021.1(36)1. 8.张志威. 参赞化育于天地间的太亟拳--从「禹步」说起. 人文艺术学院,中国语文学系 台湾台北. 2016年北京国际武术与养生文化学术研讨会论文集 9.推荐一个视频链接在哔哩哔哩,有兴趣时间可以看看UP主怎么实现的高阶皮亚诺曲线的生成过程,一个有生命的一维曲线,慢慢爬满整个空间: https://www.bilibili.com/video/BV1nF411E7VY?spm_id_from=333.337.search-card.all.click 长街门外,清茶三两盏,修枝伐茎觅根。 迎来送往,落地践行,此地为归处。 |
|