Skip to content

Latest commit

 

History

History
493 lines (394 loc) · 43 KB

牛顿分型注释.md

File metadata and controls

493 lines (394 loc) · 43 KB

重大发现,关于点的筛选与渲染的发现

经过我一整天针对AI的拷打与观察,我发现原先的NewtonFractal类里面并不存在筛选点的代码以及计算的代码,正在我一筹莫展的时候,我问了AI一个绝望的问题: 原先的class NewtonFractal(Scene):里面,那一部分负责了数据的删选与渲染 这一句话给我炸出来了真正的真相,原来筛选数据的代码确实不在原先的文件里面,而是被指向了一串扑朔迷离的代码:

  "shader_folder": "newton_fractal",
        "shader_dtype": [
            ("point", np.float32, (3,)),
        ],

AI说原先的分形部分由这部分着色器进行点的筛选以及数据的渲染。 这也是仅仅传入以下参数即可渲染的真相:

  return NewtonFractal(
      plane,
      colors=colors,
      coefs=self.coefs,
      n_steps=n_steps,
  )

你看,我们仅仅只是传入了多项式的系数矩阵,再传入了着色器,以及一个承载着全村希望的平面,而原先的没有任何的计算方面的类class NewtonFractal(Scene):就能输出一个Mobject类型的形状,,是不是很神奇?没算得结果,是不是很神奇。 再经过我的严刑拷打。我从AI那里套来了风口:着色器的路径在哪里?。然后我把3B1B的manimgl里面翻了个底朝天,终于找到了着色器的路径,也就是manimlib\shaders\newton_fractal。如果你想要知道在3B1B的男生自用版本manim里面有没有着色器的路径,我告诉你也是有的,点进首页,可以翻到manimlib/shaders/newton_fractal这个路径,下属双文件frag.glsl以及vert.glsl文件就是我们所寻找的着色器。需要知道的是鉴于manimce现在还是在使用OpenGL来进行渲染的引擎,我们可以大胆假设,现在的manimce还是支持以前的shader 进行渲染的,或者我们也可以把原先的shader里面的筛选数据的方法提取出来,来对现在的NewtonFractal进行重构。 也就是经过将近半个星期的挣扎,我们最后知道了NewtonFractal类的最后的秘密: 它根据传入的着色器来进行点的筛选与真正的渲染输出设置 怪不得我就算这么重构原先的NewtonFractal类也没用,原来是没关注那个不怎么起眼的shader。着色器着色器! 现在摆在我面前的路有两条,一条是详细的看完两个着色器文件,并且把里面渲染的方案提取出来,发到NewtonFractal以供众人观赏。第二种是直接导入原先的着色器,,,但是鉴于我完全不熟悉什么是着色器,我决定先试试第一种方法,说起来第一种方法貌似还是要看着色器啊,哎不管了先试试再说吧。我找到了manim中文的帮助文档里面有两节说明了shader里面的语法,到时候我看看能不能开始移植。

关于着色器的一些打算

好难,要不还是复写方法吧,而且里面貌似还调用了uniform方法,总不能我再去复写一遍uniform方法吧。现在的Mobject里面根本就没有针对Mobject的东西啊kora。 但是复写方法也有一个问题,我该怎么针对Mobject内部的点以及边界的点进行渲染上色呢?我该怎么自定义一个Mobject变量呢?这些都是亟待解决的问题。

Searching the web for: manimce Mobject 颜色 Searching the web for: manimce Mobject pixel color

您好,这是Bing。感谢您对manimce的兴趣。根据我搜索到的结果,有一些可能的方法可以通过自定义manimce里面的Mobject里面的特性来编辑Mobject里面特定像素的颜色。以下是一些我找到的相关信息:

  • 一种方法是使用ImageMobject类,它可以从一个numpy数组或一个文件中显示一个图像^1^。您可以通过设置scale_to_resolution参数来控制图像的分辨率,以及通过设置invert参数来反转图像的颜色。您还可以使用get_pixel_array方法来获取图像的像素数组,然后根据需要修改它。例如,您可以使用numpy的索引和切片功能来选择和改变特定区域的颜色。
  • 另一种方法是使用VMobject类,它是一个矢量化的mobject^2^。您可以通过设置fill_color和stroke_color参数来控制mobject的填充颜色和描边颜色,以及通过设置sheen_factor和sheen_direction参数来创建一个渐变效果。您还可以使用get_anchors_and_handles方法来获取mobject的贝塞尔曲线的锚点和控制点,然后根据需要修改它们。例如,您可以使用interpolate_color方法来在两个ImageMobject之间插值颜色。
  • 还有一种方法是使用Camera类,它是用来捕捉场景中的mobjects并渲染成像素数组的类^3^。您可以通过设置background_image参数来为场景添加一个背景图像,以及通过使用color_using_background_image方法来让mobjects根据背景图像的颜色进行着色。您还可以使用display_multiple_background_colored_vmobjects方法来显示多个与背景颜色相同的mobjects。

以上是我能够为您提供的一些关于如何通过自定义manimce里面的Mobject里面的特性来编辑Mobject里面特定像素的颜色的信息。希望这对您有所帮助。如果您有其他问题,请随时与我联系。😊

Learn more: 1. docs.manim.community 2. docs.manim.community 3. docs.manim.community 4. zhuanlan.zhihu.com 5. docs.manim.community 6. docs.manim.community 7. zhuanlan.zhihu.com

关于着色器newton_fractal_shader

着色器的主函数首先按照以下手法进行了声明: 声明了coefs二维系数数组(因为这里是C语言),数组的维度是MAX_DEGREE+1,因为此时会出现一个0次常数项 声明了维数为MAX_DEGREEroots的二维根数组,用来储存使用牛顿法解决的多项式的根 声明了维数为MAX_DEGREE的四维数组colors,用来储存颜色 声明了二维变量z,获取了像素在二维平面上的坐标。 后面的if语句使用一个is_parameter的变量用来说明我们所描述的空间是否为参数空间,如果是的话就按照下面的方式进行操作:

  • 把我们所得到的像素的坐标赋值给二维roots根数组里面的第三个元素

  • 再把预先传入的根数组里面的前三个元素提前赋值给r0/r1/r2三个二维变量

  • 之后根据roots里面的前三个元素,假设这三个元素是方程$coef[0]+coef1*z+coef2*z^2+coef3*z^3$的根,我们把原先的多项式$(z-r_{1})(z-r_{2})(z-r_{3})$进行展开得到的系数分别使用complex_mult进行复数的乘法计算,之后再相加相减赋值到对应的系数数列里面

  • 最后我们把z本身的值换成coef数列里面第三个元素的负三分之一(也就是三个根的中心点的位置)进行返回。作者你真的是个查总在这里设一个坑 那你可能想问了,那如果我不是参数空间的话我会怎么样? 额,没事,我们还能编。 现在我们声明了一个浮点数变量n_iters。 然后我们声明了一个二维数组变量found_root,其储存了后面那个函数的结果,那个结果是什么呢?

    • 这个函数接手了原先的二维数组z值,这个值有可能是原先的屏幕像素点坐标,也有可能已经被重整化成为了某的三个根的中点坐标了,我们该怎么办呢?这里先按照屏幕的像素点的坐标来进行预估。之后接受了最开始2*n维的系数数组coefs,再接受了n_steps,之后是n_iterss_steps还接受了转化为整形变量的步骤

    • 函数内部定义变量都是浮点数的变量值,last_len是通过使用牛顿法进行求根迭代时候的步长储存,curr_len同理,threshold是精度,即迭代过程中如果步长小于这个数字的话,就会跳脱循环计算,其被初始化为关键字CLOSE_ENOUGH后者被赋值为$10^{-3}$

    • 首先函数下方对我们输入的z值的循环进行次数至多为max_step的循环迭代,每次迭代的时候更新前进的步长与z,如果步长小于我们需要的精度或者迭代次数大于n的时候就会被跳脱循环。迭代过程中的z会更接近于指定系数多项式的根。在循环被跳出的时候计算n_iters -= log(curr_len) / log(threshold)更新此时的输入的n_iters值。代码中的 n_iters -= log(curr_len) / log(threshold); 这行代码用于调整迭代次数 n_iters。在对函数输入变量 n_iters 时候前面的 out 表示该变量是输出变量。

      在 GLSL 中,函数可以有输入变量和输出变量。输入变量是函数需要的参数,输出变量是函数计算的结果。

      在您提供的代码中,函数 seek_root() 有两个输入变量:zcoefs。函数的输出是一个变量 found_root,表示找到的根。

      out 关键字用于声明输出变量。在 seek_root() 函数中,out 关键字用于声明变量 n_iters。这意味着,函数 seek_root() 将会计算 n_iters 的值,并将其存储在 n_iters 变量中。

      seek_root() 函数的最后一行,变量 n_iters 的值被赋值给输出变量。这意味着,函数 seek_root() 的调用者可以通过输出变量 n_iters 获取找到根的迭代次数。

      以下是 seek_root() 函数的声明:

      vec2 seek_root(vec2 z, vec2[MAX_DEGREE + 1] coefs, out float n_iters){

      seek_root() 函数的调用中,输出变量 n_iters 的值可以通过 out 关键字来获取:

      float n_iters;
      vec2 found_root = seek_root(z, coefs, out n_iters);

      这行代码将 n_iters 变量初始化为零。然后,调用函数 seek_root()。函数 seek_root() 将计算 n_iters 的值,并将其存储在 n_iters 变量中。最后,函数 seek_root() 返回找到的根。

      通过 out 关键字,函数调用者可以获取函数计算的结果。

    • log(curr_len) 表示当前步长向量 curr_len 的自然对数。

      log(threshold) 表示阈值 threshold 的自然对数。

      n_iters -= log(curr_len) / log(threshold); 这行代码将 n_iters 减去 log(curr_len) / log(threshold) 的值。

      这行代码的作用是,如果当前步长向量 curr_len 很小,那么 log(curr_len) / log(threshold) 的值就很小。因此,n_iters -= log(curr_len) / log(threshold); 这行代码将 n_iters 减去一个很小的值。这意味着,如果当前步长向量 curr_len 很小,那么迭代次数 n_iters 就会降低。 通过计算log(curr_len) / log(threshold)的比率,可以得到一个表示迭代的精度或收敛速度的浮点值。

      通过将这个比率从n_iters中减去,可以进一步调整迭代次数,使其更准确地反映迭代过程的持续时间或精确度。

      总体而言,n_iters -= log(curr_len) / log(threshold)的目的是为了根据迭代过程中的收敛情况,对迭代次数进行修正,以便更准确地表示迭代过程的持续时间或精度。

    • 我们回到刚才的进程中,found_root变量是指我们找到了我们传入的屏幕上的像素点数的坐标所代表的点数的根数,同时我们得到了通过这个值算出来的根时候所需要的迭代次数的迭代次数n_iters,这个迭代次数非常的关键,我们后续要通过它来进行上色处理。

    • 在求根之后我们再声明两个变量,一个是2*4的四维数组colors,来储存颜色。另一个是迭代后的距离最小值min_dist,其被初始化为$10^4$,代表距离我们规定的三个根的最小值。

    • 现在我们遍历三个根,我们所熟知的三个根。然后分别计算屏幕上面的像素的坐标在经过多次迭代之后的fount_root和三个根之间的哪一个更相近。之后把最小值赋值给min_dist,并且用colors里面的第i个颜色的值传给这个像素点。

    • 但是这个像素点的上色并没有完我们后面还有一个表达式来对其进行进一步的后处理。根据迭代步数n_iters和饱和度系数saturation_factor给color添加饱和度效果。如果black_for_cycles大于0且最小距离大于阈值,则color设为纯黑色(0,0,0,1)

    • 到了这一步,我们上色还没有完,我们还有一个后处理,也就是julia_highlight茱莉亚高光项。其实就是给点添加几个不同方向的偏置,经过一次牛顿法的迭代在 color *= 1.0 * smoothstep(0, 0.1, max_dist); 这行代码中,smoothstep() 函数将 max_dist 映射到 [0, 1] 之间。如果 max_dist 小于 0.1,那么 smoothstep() 函数将返回 1。如果 max_dist 大于 0.1,那么 smoothstep() 函数将返回一个介于 01 之间的值。 color *= 1.0 * smoothstep(0, 0.1, max_dist); 这行代码将 color 变量乘以 smoothstep() 函数的输出值。这意味着,如果 max_dist 小于 0.1,那么 color 变量将不变。如果 max_dist 大于 0.1,那么 color 变量的饱和度将降低。 在 Julia 集合渲染中,迭代次数越多,max_dist 的值就越小。因此,如果 max_dist 小于 0.1,那么找到的根与多项式根非常接近。这意味着,像素的颜色应该是饱和的。

  • 1 着色器是怎么筛选出来点的? 这是最关键的问题,因为着色器能够按照像素来渲染吗?放大的话会失真吗? 着色器通过xyz_coords.xy确定了每一个点的像素位置并分别给其上色,在经过牛顿法迭代的平面上的点,最后距离哪个根近就会被赋值给哪个点的颜色。

  • 2 我们能把平面上的点分成几类? 按理来说应该是三类,第一类是发散的点,这些点的处理手段比较简单,我们按照它被判定为发散时候的迭代次数进行上色就行。第二类是集合里面的点,这个直接上成黑色就可以。第三类比较复杂,就是处在边界上的点。我们之前在2x2的平面上做过实验,就算是以0.02为步长来进行渲染,也很难找出刚好使得迭代之后的幅值等于发散值的点,迭代之后的幅值要么比规定的发散幅值要大,要么要小。对于这种情况,我的看法是,边界上的点其实并不是我们真的算出来的,是按照我们定好的锚点,然后被插值形成的。真正的关键就是如何订好这个锚点,如何找到这些锚点。 我决定了,直接使用ImageMobject类的图像进行创作,以及颜色渲染,首先的第一步就是如何定义ImageMobject

自定义ImageMobject类型变量样例

ImageMobject的特色是能够使用使用NumberPYnparray数列来进行针对屏幕上的点进行渲染。

class GradientImageFromArray(Scene):
    def construct(self):
        n = 256
        imageArray = np.uint8(
            [[i * 256 / n for i in range(0, n)] for _ in range(0, n)]
        )
        image = ImageMobject(imageArray).scale(2)
        image.background_rectangle = SurroundingRectangle(image, GREEN)
        self.add(image, image.background_rectangle)

样例二:

class ImageInterpolationEx(Scene):
    def construct(self):
        img = ImageMobject(np.uint8([[63, 0, 0, 0],
                                        [0, 127, 0, 0],
                                        [0, 0, 191, 0],
                                        [0, 0, 0, 255]
                                        ]))

        img.height = 2
        img1 = img.copy()
        img2 = img.copy()
        img3 = img.copy()
        img4 = img.copy()
        img5 = img.copy()

        img1.set_resampling_algorithm(RESAMPLING_ALGORITHMS["nearest"])
        img2.set_resampling_algorithm(RESAMPLING_ALGORITHMS["lanczos"])
        img3.set_resampling_algorithm(RESAMPLING_ALGORITHMS["linear"])
        img4.set_resampling_algorithm(RESAMPLING_ALGORITHMS["cubic"])
        img5.set_resampling_algorithm(RESAMPLING_ALGORITHMS["box"])
        img1.add(Text("nearest").scale(0.5).next_to(img1,UP))
        img2.add(Text("lanczos").scale(0.5).next_to(img2,UP))
        img3.add(Text("linear").scale(0.5).next_to(img3,UP))
        img4.add(Text("cubic").scale(0.5).next_to(img4,UP))
        img5.add(Text("box").scale(0.5).next_to(img5,UP))

        x= Group(img1,img2,img3,img4,img5)
        x.arrange()
        self.add(x)

Searching the web for: manim ImageMobject multiple colors

是的,你可以让ImageMobject显示更多种不同的颜色。在创建ImageMobject时,你需要提供一个三维的numpy数组,其中第三个维度的大小为3或4,分别对应RGB或RGBA颜色模式^1^

例如,下面的代码创建了一个包含红色和蓝色的图像:

image = ImageMobject(np.uint8([
    [[255, 0, 0], [0, 0, 255]],
    [[255, 0, 0], [0, 0, 255]]
]))

在这个例子中,[255, 0, 0]代表红色,[0, 0, 255]代表蓝色。你可以通过改变这些值来生成任何你想要的颜色^1^

希望这个解释对你有所帮助!如果你有其他关于ManimCE或者其他编程问题,欢迎随时向我提问。

Learn more: 1. docs.manim.community 2. docs.manim.community 3. docs.manim.community 4. docs.manim.community

关于NewtonFractal分型渲染茱莉亚集合的输入分析

manimlib document官方文档地址 manim中文

    j_plane = self.get_plane()
    j_plane.set_y(jy)
    julia = NewtonFractal(j_plane, coefs=coefs, colors=5 * [GREY_A])
    julia.set_julia_highlight(1e-3)
    j_group = Group(julia, j_plane)

NewtonFractal分型需要输入:

  • 一个平面j_plane属性,用于确定渲染的范围。j_plane的值被get_plane方法初始化,get_plane方法隶属于Scene类。
  • 一个系数列表coef属性,用于确定系数(话说真的需要确定有理多项式的系数吗)
  • 一个颜色的初始化参数colors属性(是列表吗?还是什么其它的东西) 在被传入后貌似不需要再进行初始化操作,NewtonFractal就能自动返回一个Mobject类型结果。 重点调研这几个对象在被传入后所被进行的操作。尤其是NumberPlane类型下属的ComplexPlane类型对象在被传入后发生了什么,跟我最开始一个一个点的传入相比有什么异同? 在被传入之后,需要调用set_julia_highlight方法设置高亮 在此时,j_group这个Mobject已经被初步设置完毕,可以在Scene.play里面调用动画的呈现方式GrowFromCenter来针对其进行渲染。

get_plane方法与被传入的plane属性

此方法返回一个CompelxNumber类型的平面对象,此类型隶属于NumberPlane类。 通过跟踪NewtonFractal的声明的基本属性我发现了以下事情。

  • 首先,NewtonFractal是拥有一个初始化__init__的特殊方法的,这也是其在被实例化之后不需要接受其其它方法进行后续处理的主要原因。
  • 其次,初始化特殊方法接受了plane类型的传入,并且对其进行了相应的操作。其把plane变量进行了缩放,并且将自己的偏置放在了中心,这一步是为了将自己放在显示平面的中心,但是现在新版本的manimce里面有使用质心的方法来达到这个目的,我们说不定能进行改进。 综上所述,在初始化方法里面,我们并没有看到其对plane参数进行进一步的设置。这一切可能要靠后面的系数参数来进行规整。

在 GLSL 中,向量可以用来表示位置、颜色、速度等。

在您提供的代码中,变量 z 是一个二维向量,表示 Julia 集合的点。

coef属性

进一步观察Mobject下属的NewtonFractal类我们会发现,其除了最开始的__init__初始化方法,还有其它的初始化方法在起着作用。

  def init_data(self):
          self.set_points([UL, DL, UR, DR])

这句定义隶属于其父类Mobject下属的Mobject->types->mobject.py的初始化方法__init__

  def __init__:
      ...
          def init_data(self, length: int = 0):
              self.data = np.zeros(length, dtype=self.shader_dtype)

上面的那一段初始化方法的意思是初始化所有点的坐标,接受一个名为length的初始化数组长度的数字,然后将数组里面的数字统统归零,最后数据的类型变为shader_dtype

另一段初始化方法:

    def init_uniforms(self):
        super().init_uniforms()
        self.set_colors(self.colors)
        self.set_julia_highlight(self.julia_highlight)
        self.set_coefs(self.coefs)
        self.set_scale(self.scale_factor)
        self.set_offset(self.offset)
        self.set_n_steps(self.n_steps)
        self.set_saturation_factor(self.saturation_factor)
        self.set_opacity(self.opacity)
        self.uniforms["black_for_cycles"] = float(self.black_for_cycles)
        self.uniforms["is_parameter_space"] = float(self.is_parameter_space)

其也隶属于其父类Mobject下属的Mobject->types->mobject.py的初始化方法__init__

  def __init__:
      def init_uniforms(self):
          self.uniforms: UniformDict = {
              "is_fixed_in_frame": float(self._is_fixed_in_frame),
              "shading": np.array(self.shading, dtype=float),
          }

上面那一段代码的意思是init_uniforms方法创建一个名为self.uniforms的字典,这个字典包含了两个键值对:

  • "is_fixed_in_frame":这个键对应的值是self._is_fixed_in_frame的浮点数形式。这个值表示Mobject对象是否在帧中固定。
  • "shading":这个键对应的值是self.shading的numpy数组形式,数据类型为浮点数。这个值表示Mobject对象的阴影。 这意味着在事实上,就算我们没有直接把这些代码直接放入NewtonFractal的初始化方法里面,其也会因为实例化时直接调用父类的初始化方法代码里面的方法而被自动的调用,这也是不需要对其实例化之后再调用里面的其它方法对其进行进一步的加工的主要原因,因为其会自动调用这两个方法对自己进行初始化。

前置方法

  • poly(x, coefs):这是一个多项式函数,它接受一个数值x和一个系数列表coefs,然后返回多项式在x处的值。多项式的形式为 coefs[0]*x^0 + coefs[1]*x^1 + coefs[2]*x^2 + ...

  • dpoly(x, coefs):这是多项式函数的导数,它也接受一个数值x和一个系数列表coefs,然后返回多项式在x处的导数值。

  • roots_to_coefficients(roots):这个函数接受一个根的列表roots,然后返回对应的多项式系数。这里使用了组合数学和numpy的乘积函数。 这是一个用于根据根生成系数的函数,它接受一个根的列表roots作为参数,返回一个系数列表coefs,使得f(x) = (x - roots[0]) * (x - roots1) * … * (x - roots[n-1])

  • find_root(func, dfunc, seed=complex(1, 1), tol=1e-8, max_steps=100):这个函数使用牛顿法来寻找给定函数func的根。它还需要一个导数函数dfunc,一个初始猜测值(种子)seed,一个容忍误差tol,以及最大迭代次数max_steps。如果连续两次迭代的结果之间的差小于容忍误差,或者达到最大迭代次数,则停止迭代。

  • coefficients_to_roots(coefs):这个函数接受一个系数列表coefs,然后返回对应的多项式根。 这是一个用于根据系数生成根的函数,它使用find_root函数运用牛顿法来逐个寻找根。它接受一个系数列表coefs作为参数,返回一个根列表roots,从而解得方程$coefs[0] + coefs1 * x + coefs2 * x^2 + … + coefs[n] * x^n = 0$

NewtonFractal

这段代码定义了一个名为 NewtonFractal 的类,它是 VMobject 类的子类。这个类用于创建和操作牛顿分形图像。

类的属性包括:

  • shader_folder:着色器文件夹的名称。
  • shader_dtype:着色器数据类型。
  • colors:分形图像的颜色。
  • coefs:多项式的系数。
  • scale_factor:缩放因子。
  • offset:偏移量。
  • n_steps:迭代步数。
  • julia_highlight:朱利亚高亮值。
  • max_degree:最大度数。
  • saturation_factor:饱和度因子。
  • opacity:透明度。
  • black_for_cycles:是否为循环设置黑色背景。
  • is_parameter_space:是否为参数空间。

类的方法包括:

  • __init__():初始化方法,用于设置对象的初始状态。
  • init_data():初始化数据,设置点的位置。
  • init_uniforms():初始化统一变量,设置颜色、高亮、系数、缩放、偏移、步数、饱和度和透明度等属性。
  • 以及一系列的 set_ 方法,用于设置各种属性。

这个类主要用于创建和操作牛顿分形图像,通过改变各种属性来改变分形图像的外观。例如,可以通过改变颜色、透明度、饱和度等来改变分形图像的颜色;可以通过改变系数来改变分形图像的形状;可以通过改变缩放和偏移来改变分形图像的大小和位置等。这个类提供了一种灵活且强大的方式来创建和操作牛顿分形图像。

这个PYTHON代码定义了一个名为NewtonFractal的类,它是VMobject的子类,用于创建和显示牛顿分形图像。牛顿分形是一种使用牛顿法求解复数多项式方程根的方法,根据不同的根和迭代次数给平面上的点着色。这个类的属性和方法有:

  • shader_folder: 一个字符串,表示存放着色器文件的文件夹的名称,用于渲染分形图像。
  • shader_dtype: 一个列表,表示着色器数据类型,包含一个元组,元组中有三个元素:("point", np.float32, (3,)),表示每个点的数据是一个包含三个浮点数的数组,分别表示点的x, y, z坐标。
  • colors: 一个列表,表示用于着色不同根的颜色,使用ROOT_COLORS_DEEP常量定义。
  • coefs: 一个列表,表示复数多项式方程的系数,从最高次到最低次排列,例如[1.0, -1.0, 1.0, 0.0, 0.0, 1.0]表示方程z^5 + z^2 - z + 1 = 0。
  • scale_factor: 一个浮点数,表示分形图像的缩放因子,用于调整图像的大小。
  • offset: 一个数组,表示分形图像的偏移量,用于调整图像的位置。
  • n_steps: 一个整数,表示牛顿法迭代求解根的最大步数,用于控制图像的精度和效果。
  • julia_highlight: 一个浮点数,表示朱利亚集高亮效果的强度,用于在分形图像中突出显示朱利亚集的边界。
  • max_degree: 一个整数,表示复数多项式方程的最大次数,用于限制方程的复杂度。
  • saturation_factor: 一个浮点数,表示分形图像的饱和度因子,用于调整图像的颜色饱和度。
  • opacity: 一个浮点数,表示分形图像的透明度,用于调整图像的可见度。
  • black_for_cycles: 一个布尔值,表示是否使用黑色来着色那些在迭代过程中出现周期性行为的点,用于显示分形图像中的奇异点。
  • is_parameter_space: 一个布尔值,表示是否将分形图像视为参数空间,用于在参数空间中显示不同系数对应的分形图像。

这个类有以下方法:

  • init(self, plane, ** kwargs): 这是类的构造方法,接受一个plane对象作为参数,表示要在哪个平面上显示分形图像,并使用**kwargs传递其他属性值。这个方法调用了父类VMobject的__init__方法,并使用replace方法将自身替换为plane对象,并设置stretch为True。
  • init_data(self): 这是类的初始化数据方法,设置了自身的points属性为[UL, DL, UR, DR]四个点,表示平面上左上、左下、右上、右下四个角落的位置。
  • init_uniforms(self): 这是类的初始化统一变量方法,调用了父类VMobject的init_uniforms方法,并使用set_colors, set_julia_highlight, set_coefs, set_scale, set_offset, set_n_steps, set_saturation_factor, set_opacity等方法设置了自身的uniforms属性中各个统一变量的值。统一变量是一种在着色器程序中使用的变量类型,它们在所有顶点或片段中保持不变。这些统一变量包括了颜色、高亮、系数、根、缩放、偏移、步数、饱和度、透明度等信息。
  • set_colors(self, colors): 这是类的设置颜色方法,接受一个颜色列表作为参数,并更新自身的uniforms属性中的color{n}变量的值,n是颜色的索引。参数 colors 是一个颜色列表。set_colors()方法将 colors 中的颜色更新到 mobjectuniforms 属性中。
   class MyMobject(Mobject):
    def __init__(self):
        super().__init__()
        self.set_colors(["red", "green", "blue"])

m = MyMobject()
print(m.uniforms)

输出为:

{‘color0’: array([1., 0., 0., 1.], dtype=float32), ‘color1’: array([0., 1., 0., 1.], dtype=float32), ‘color2’: array([0., 0., 1., 1.], dtype=float32)}

mobjectuniforms 属性是一个字典,用于存储mobject的所有变量,这些变量可以通过Manim的属性动画(Property Animation)系统进行动画处理。

set_colors()方法首先将 colors 中的颜色转换为RGBA颜色。然后,它将 RGBA 颜色存储在 mobjectuniforms 属性中,键名为 f"color{n}",其中 n 是颜色的索引。

最后,set_colors()方法返回 mobject 本身。这个方法返回自身对象,以便进行链式调用。

方法返回self方便链式调用的原因是,链式调用是指在一个语句中连续调用多个方法,而每个方法的返回值都是下一个方法的调用对象。如果方法返回self,就相当于返回了当前对象本身,这样就可以继续在后面添加其他方法,形成一个链式结构。

例如,假设有一个类Person,它有三个方法:say_hello, introduce, say_bye,分别用于打招呼、自我介绍和道别。如果这三个方法都返回self,那么可以这样写:

  p = Person('Alice')
  p.say_hello().introduce().say_bye()

这样就可以在一行代码中完成三个操作,而不需要分开写:

  p = Person('Alice')
  p.say_hello()
  p.introduce()
  p.say_bye()

这样的好处是可以使代码更简洁、清晰和易读,也可以避免重复输入对象的名字。 此处的uniforms字典是在一开始的self.init_uniform()里面所添加的一个字典,并且此处使用了字典下属的update方法来对字典进行进一步的更新。Python 字典里面的 update() 方法用于更新字典。

update() 方法的语法是:

dict.update(dict2)

参数 dict2 是另一个字典。update() 方法将 dict2 中的键值对更新到 dict 中。

如果 dict2 中存在 dict 中已经存在的键,则 update() 方法将 dict2 中的值覆盖 dict 中的值。

  • set_julia_highlight(self, value): 这是类的设置朱利亚集高亮方法,接受一个浮点数作为参数,并更新自身的uniforms属性中的julia_highlight变量的值。这个方法返回自身对象,以便进行链式调用。
  • set_coefs(self, coefs, reset_roots=True): 这是类的设置系数方法,接受一个系数列表和一个布尔值作为参数,并更新自身的uniforms属性中的coefn变量的值,其中n是从0到最大次数的整数,coefn是一个包含两个浮点数的数组,表示复数系数的实部和虚部。首先,这个方法创建一个名为full_coefs的列表,这个列表包含了输入的系数coefs,以及一些零,使得列表的长度等于self.max_degree + 1(就是为了补足0到最高阶次,这个算法的最高阶次max_degree默认为5)。如果reset_roots为True,则调用set_roots方法根据系数计算并设置根的值,否则不改变根的值。这个方法使用了complex函数将系数转换为复数。这个方法返回自身对象,以便进行链式调用。
  • set_roots(self, roots, reset_coefs=True): 这是类的设置根方法,接受一个根列表和一个布尔值作为参数,并更新自身的uniforms属性中的n_roots和rootn变量的值,其中n_roots是一个浮点数,表示根的个数,rootn是一个包含两个浮点数的数组,表示复数根的实部和虚部。如果reset_coefs为True,则调用set_coefs方法根据根计算并设置系数的值,否则不改变系数的值。这个方法使用了complex函数将根转换为复数。这个方法返回自身对象,以便进行链式调用。
  • set_scale(self, scale_factor): 这是类的设置缩放方法,接受一个浮点数作为参数,并更新自身的uniforms属性中的scale_factor变量的值。这个方法返回自身对象,以便进行链式调用。
  • set_offset(self, offset): 这是类的设置偏移方法,接受一个数组作为参数,并更新自身的uniforms属性中的offset变量的值。这个方法返回自身对象,以便进行链式调用。
  • set_n_steps(self, n_steps): 这是类的设置步数方法,接受一个整数作为参数,并更新自身的uniforms属性中的n_steps变量的值。这个方法返回自身对象,以便进行链式调用。
  • set_saturation_factor(self, saturation_factor): 这是类的设置饱和度方法,接受一个浮点数作为参数,并更新自身的uniforms属性中的saturation_factor变量的值。这个方法返回自身对象,以便进行链式调用。
  • set_opacities(self, *opacities): 这是类的设置透明度方法,接受若干个浮点数作为参数,并更新自身的uniforms属性中colorn变量中第四个元素(即透明度)的值。这个方法返回自身对象,以便进行链式调用。
  • set_opacity(self, opacity, recurse=True): 这是类的设置透明度方法,接受一个浮点数和一个布尔值作为参数,并调用set_opacities方法将所有颜色的透明度设置为相同的值。如果recurse为True,则还会递归地调用子对象(如果有)的set_opacity方法。这个方法返回自身对象,以便进行链式调用。

关键在于Newton Fractal和Scene里面的get_fractals方法里面

get_fractals()方法

get_fractals()方法的主要作用是生成Fatou集和Julia集。Fatou集是指那些在迭代过程中不会逃逸到无穷大的点组成的集合。Julia集是指那些在迭代过程中会逃逸到无穷大的点组成的集合。

Fatou集和Julia集的形状取决于多项式的系数。在这个方法中,多项式的系数为roots_to_coefficients([-1.5, 1.5, 1j, -1j])

get_fractals()方法首先创建一个VGroup来包含Fatou集的各个部分。然后,它创建一个Group来包含Fatou集的各个部分和Julia集。最后,它为每个Fatou集和Julia集添加一个updater,以确保它们的位置和大小与各自的平面相同。

get_fractals()方法的输出是一个Group,其中包含Fatou集和Julia集。

以下是对get_fractals()方法中的一些关键代码的解释:

coefs = roots_to_coefficients([-1.5, 1.5, 1j, -1j])
n = len(coefs) - 1

这部分代码计算多项式的系数和次数。

f_planes = VGroup(*(self.get_plane() for x in range(n)))
f_planes.arrange(RIGHT, buff=LARGE_BUFF)

这部分代码创建一个VGroup来包含Fatou集的各个部分。Fatou集的各个部分是通过调用self.get_plane()来创建的。self.get_plane()返回一个平面,其中包含一个单位圆。

plusses = [Tex("+") for _ in range(n - 1)]  # Tex("+").replicate(n - 1)
f_group = Group(*it.chain(*zip(f_planes, plusses)))
f_group.add(f_planes[-1])
f_group.arrange(RIGHT)

这部分代码创建一个Group来包含Fatou集的各个部分和Julia集。f_group包含n个平面,每个平面代表Fatou集的一个部分。f_group还包含一个VGroup,其中包含n - 1个加号。加号用于将Fatou集的各个部分连接起来。

fatou = Group(
            *(
                NewtonFractal(f_plane, coefs=coefs, colors=colors)
                for f_plane in f_planes
            )
        )

这部分代码为Fatou集的各个部分创建NewtonFractal对象。

for i, fractal in enumerate(fatou):
            opacities = n * [0.2]
            opacities[i] = 1
            fractal.set_opacities(*opacities)

这部分代码设置Fatou集的各个部分的不透明度。只有当前的Fatou集部分是不透明的,其他部分都是透明的。

j_plane = self.get_plane()
j_plane.set_y(jy)
julia = NewtonFractal(j_plane, coefs=coefs, colors=5 * [GREY_A])
julia.set_julia_highlight(1e-3)
j_group = Group(julia, j_plane)

这部分代码为Julia集创建NewtonFractal对象。

for fractal, plane in zip((*fatou, julia), (*f_planes, j_plane)):
            fractal.plane = plane
            fractal.add_updater(
                lambda m: m.set_offset(m.plane.get_center())
                .set_scale(m.plane.get_x_unit_size())
                .replace(m.plane)
            )

这部分代码为每个Fatou集和Julia集添加一个updater,以确保它们的位置和大小与各自的平面相同。

fractals = Group(f_group, j_group)
return fractals

这部分代码返回一个Group,其中包含Fatou集和Julia集。

您好,这是Bing。我可以为您解释这段代码的作用和输出。这段代码是定义一个类方法的,它的参数是jy和fy,分别表示Julia集和Fatou集的y轴位置。这个方法的主要作用是生成一组分形图像,包括n个Fatou集和一个Julia集,其中n是多项式的次数。这个方法的输出是一个Group对象,它包含了所有分形图像和对应的平面对象。

具体来说,这段代码的步骤如下:

  • 首先,定义一个coefs变量,它是一个列表,表示多项式的系数。这里使用roots_to_coefficients方法,根据多项式的根来计算系数。这里的根是[-1.5, 1.5, 1j, -1j],表示四个复数。
  • 然后,定义一个n变量,它是一个整数,表示多项式的次数。这里使用len(coefs) - 1来计算次数,因为系数列表的长度比次数多一。
  • 接着,定义一个colors变量,它是一个列表,表示分形图像的颜色。这里使用self.colors属性来赋值。
  • 然后,定义一个f_planes变量,它是一个VGroup对象,表示n个Fatou集对应的平面对象。这里使用self.get_plane方法来创建每个平面对象,并且使用VGroup方法来将它们组合在一起。然后使用arrange方法来将它们按照右对齐的方式排列,并且设置缓冲区为LARGE_BUFF。
  • 接着,定义一个plusses变量,它是一个列表,表示n - 1个加号符号对象。这里使用Tex方法来创建每个加号符号对象,并且使用列表推导式来生成列表。
  • 然后,定义一个f_group变量,它是一个Group对象,表示Fatou集和加号符号的组合。这里使用Group方法来创建对象,并且使用it.chain和zip方法来将f_planes和plusses交错地添加到对象中。然后再添加f_planes中的最后一个元素,并且使用arrange方法来将它们按照右对齐的方式排列。
  • 接着,定义一个fatou变量,它是一个Group对象,表示n个Fatou集的分形图像对象。这里使用NewtonFractal方法来创建每个分形图像对象,并且传入对应的平面对象、系数列表和颜色列表作为参数。然后使用Group方法来将它们组合在一起。
  • 然后,对于每个分形图像对象,设置其透明度属性。这里使用enumerate方法来遍历fatou中的元素,并且获取其索引和值。然后定义一个opacities变量,它是一个列表,表示n个分形图像对象的透明度值。这里将所有元素的值设为0.2,除了当前索引对应的元素设为1.0。然后使用set_opacities方法来设置每个分形图像对象的透明度属性。
  • 然后,将fatou中的所有元素添加到f_group中,并且使用set_y方法来设置f_group的y轴位置为fy参数的值。
  • 接着,定义一个j_plane变量,它是一个Mobject对象,表示Julia集对应的平面对象。这里使用self.get_plane方法来创建平面对象,并且使用set_y方法来设置其y轴位置为jy参数的值。
  • 然后,定义一个julia变量,它是一个NewtonFractal对象,表示Julia集的分形图像对象。这里使用NewtonFractal方法来创建分形图像对象,并且传入j_plane、coefs和5个GREY_A颜色作为参数。然后使用set_julia_highlight方法来设置其高亮属性为1e-3。
  • 接着,定义一个j_group变量,它是一个Group对象,表示Julia集和平面的组合。这里使用Group方法来创建对象,并且添加julia和j_plane作为元素。
  • 然后,对于每个分形图像对象和对应的平面对象,设置其更新函数。这里使用zip方法来遍历fatou和julia中的元素,并且获取其值。然后对于每个分形图像对象,设置其plane属性为对应的平面对象,并且使用add_updater方法来添加一个匿名函数作为更新函数。这个函数的作用是根据平面对象的中心位置、x轴单位长度和形状来调整分形图像对象的偏移量、缩放比例和替换效果。
  • 最后,定义一个fractals变量,它是一个Group对象,表示所有分形图像和平面的组合。这里使用Group方法来创建对象,并且添加f_group和j_group作为元素。然后返回这个对象作为方法的输出。

我希望这对您有所帮助。如果您有其他问题,请随时提出。😊

我决定在搞明白整个架构之后将整个模块在manimce里面进行重构,这将是很大的工程,但是我不后悔