簡體   English   中英

JavaScript 分形生成算法 - 為什么一個這么快?

[英]JavaScript fractal generation algorithms - why is one so much faster?

我正在嘗試從第一原理編寫 JavaScript 分形生成算法。 我知道那里有很多示例,但我想加入額外的功能來支持 Mandelbrot 和“旋轉”Julia 以及“Burning Ship”和“Tricorn”等變體。 考慮到這一點,我實現了一個輕量級的復雜數學庫(同樣,我知道那里有標准的復雜 js 庫,但我想從頭開始構建一個作為學習練習)。

我測試了兩個替代函數,一個使用標准數學函數的fractal ,另一個使用我的 Complex 庫方法的fractalComplex 它們都工作得很好,但我驚訝地發現標准版本的速度幾乎是復雜版本的兩倍 我期待一些額外的開銷,但不是那么多!

誰能解釋為什么? Complex 庫“在幕后”使用相同的數學結構。 額外的開銷是否完全取決於 object 的創建?

代碼如下所示(輸入參數 z 和 c 是{re, im}形式的對象)。

function fractal(z, c, maxiter) {

    var i, za, re, im, re2, im2;
    c = (settype === JULIA ? c : z);

    // Iterate until abs(z) exceeds escape radius
    for (i = 0; i < maxiter; i += 1) {

        if (setvar === BURNING_SHIP) {
            re = Math.abs(z.re);
            im = -Math.abs(z.im);
        }
        else if (setvar === TRICORN) {
            re = z.re
            im = -z.im; // conjugate z
        }
        else { // Mandelbrot
            re = z.re;
            im = z.im;
        }

        re2 = re * re;
        im2 = im * im;
        z = { // z = z² + c
            re: re2 - im2 + c.re,
            im: 2 * im * re + c.im
        };

        za = re2 + im2 // abs(z)²
        if (za > 4) { // abs(z)² > radius²
            break;
        }
    }
    za = Math.sqrt(za); // abs(z)
    return { i, za };
}

function fractalComplex(z, c, maxiter, n, radius) {

    var i, za;
    c = (settype === JULIA ? c : z);

    // Iterate until abs(z) exceeds escape radius
    for (i = 0; i < maxiter; i += 1) {

        if (setvar === BURNING_SHIP) {
            z = new Complex(Math.abs(z.re), -Math.abs(z.im))
        }
        if (setvar === TRICORN) {
            z = z.conjugate()
        }

        z = z.quad(n, c); // z = zⁿ + c
        za = z.abs();
        if (za > radius) {
            break;
        }
    }
    return { i, za };
}

我的“復雜精簡版”庫如下:

// ------------------------------------------------------------------------
// A basic complex number library which implements the methods used for
// Mandelbrot and Julia Set generation.
// ------------------------------------------------------------------------
'use strict';

// Instantiate complex number object.
function Complex(re, im) {
  this.re = re; // real
  this.im = im; // imaginary
}

Complex.prototype = {

  're': 0,
  'im': 0,

  // Set value.
  'set': function (re, im) {
    this.re = re;
    this.im = im;
  },

  // Get magnitude.
  'abs': function () {
    return Math.sqrt(this.re * this.re + this.im * this.im);
  },

  // Get polar representation (r, θ); angle in radians.
  'polar': function () {
    return { r: this.abs(), θ: Math.atan2(this.im, this.re) };
  },

  // Get square.
  'sqr': function () {
    var re2 = this.re * this.re - this.im * this.im;
    var im2 = 2 * this.im * this.re;
    return new Complex(re2, im2);
  },

  // Get complex number to the real power n.
  'pow': function (n) {
    if (n === 0) { return new Complex(1, 0); }
    if (n === 1) { return this; }
    if (n === 2) { return this.sqr(); }
    var pol = this.polar();
    var rn = Math.pow(pol.r, n);
    var θn = n * pol.θ;
    return cart(rn, θn);
  },

  // Get conjugate.
  'conjugate': function () {
    return new Complex(this.re, -this.im);
  },

  // Get quadratic zⁿ + c.
  'quad': function (n, c) {
    var zn = this.pow(n);
    return new Complex(zn.re + c.re, zn.im + c.im);
  },

  // Rotate by angle in radians.
  'rotate': function (angle) {
    var pol = this.polar();
    angle += pol.θ;
    return new Complex(pol.r * Math.cos(angle), pol.r * Math.sin(angle));
  },

  // String in exponent format to specified significant figures.
  'toString': function (sig = 9) {
    return this.re.toExponential(sig) + " + " + this.im.toExponential(sig) + "i";
  },
}

// Convert polar (r, θ) to cartesian representation (re, im).
function cart(r, θ) {
  var re = r * Math.cos(θ);
  var im = r * Math.sin(θ);
  return new Complex(re, im);
}

附加編輯 20/12/2021 12:15:

對於它的價值,這就是我最終決定的......

function fractal(p, c, maxiter) {

        var i, za, zre, zim, cre, cim, tmp;
        var lastre = 0;
        var lastim = 0;
        var per = 0;
        if (setmode === JULIA) {
            cre = c.re;
            cim = c.im;
            zre = p.re;
            zim = p.im;
        }
        else { // Mandelbrot mode
            cre = p.re;
            cim = p.im;
            zre = 0;
            zim = 0;
        }

        // Iterate until abs(z) exceeds escape radius
        for (i = 0; i < maxiter; i += 1) {

            if (setvar === BURNING_SHIP) {
                zre = Math.abs(zre);
                zim = -Math.abs(zim);
            }
            else if (setvar === TRICORN) {
                zim = -zim; // conjugate z
            }

            // z = z² + c
            tmp = zre * zre - zim * zim + cre;
            zim = 2 * zre * zim + cim;
            zre = tmp;

            // Optimisation - periodicity check speeds
            // up processing of points within set
            if (PERIODCHECK) {
                if (zre === lastre && zim === lastim) {
                    i = maxiter;
                    break;
                }
                per += 1;
                if (per > 20) {
                    per = 0;
                    lastre = zre;
                    lastim = zim;
                }
            }
            // ... end of optimisation

            za = zre * zre + zim * zim // abs(z)²
            if (za > radius) { // abs(z)² > radius²
                break;
            }
        }
        return { i, za };
}

以下 class 可能同時滿足復雜 object 的性能問題和正確封裝...

知名人士:

  • 在適用的情況下,始終返回this Complex object(即實例化對象),這有助於鏈接。 例如,

    • x = new Complex(10, 5).sqrThis().powThis(1);
  • 對於每個返回 Complex object 的 Complex 方法,配置兩 (2) 個方法:

    • 一種稱為<method>This的方法,它直接在this object 上運行並包含 function 邏輯。
    • 一種名為<method>的方法,它從this object 克隆一個新的 Complex object,然后調用<method>This以在克隆上執行 function 邏輯。
    • 這為開發人員提供了更新現有 object 或返回新 object 的方法的選擇。
  • 對其他 Complex 方法的內部調用通常應使用<method>This版本,因為初始調用確定是就地使用現有的this object,還是克隆它。 從那里,對其他 Complex 方法的所有內部調用將繼續在this object 或克隆上運行。

 // ------------------------------------------------------------------------ // A basic complex number library which implements the methods used for // Mandelbrot and Julia Set generation. // ------------------------------------------------------------------------ 'use strict'; class Complex { constructor( reOrComplex, im ) { this.set( reOrComplex, im ); } set( reOrComplex, im ) { if ( reOrComplex instanceof Complex ) { this.re = reOrComplex.re; this.im = reOrComplex.im; } else { this.re = reOrComplex; this.im = im; } return this; } abs() { return Math.sqrt(this.re * this.re + this.im * this.im); } toPolar() { return { r: this.abs(), θ: Math.atan2(this.im, this.re) }; } sqrThis() { return this.set( this.re * this.re - this.im * this.im, 2 * this.im * this.re ); } sqr() { return new Complex( this ).sqrThis(); } powThis( n ) { if ( n === 0 ) { return this.set( 1, 0 ) }; if ( n === 1 ) { return this; } if ( n === 2 ) { return this.sqrThis(); } let polar = this.toPolar(); return this.toCartesianThis( Math.pow(polar.r, n), n * polar.θ ); } pow( n ) { return new Complex( this ).powThis( n ); } conjugateThis() { return this.set( this.re, -this.im); } conjugate() { return new Complex( this ).conjugateThis(); } quadraticThis( n, c ) { let zn = this.powThis( n ); return this.set( zn.re + c.re, zn.im + c.im ); } quadratic( n, c ) { return new Complex( this ).quadraticThis( n, c ); } rotateThis( deltaAngle ) { let polar = this.toPolar(); let angle = polar.θ + deltaAngle; return this.set( polar.r * Math.cos(angle), polar.r * Math.sin(angle) ); } rotate( deltaAngle ) { return new Complex( this ).rotateThis( deltaAngle ); } toString( sig = 9 ) { return this.re.toExponential( sig ) + " + " + this.im.toExponential( sig ) + "i"; } toCartesianThis( r, θ ) { return this.set( r * Math.cos( θ ), r * Math.sin( θ ) ); } } // Convert polar (r, θ) to cartesian representation (re, im). Complex.toCartesian = function ( r, θ ) { return new Complex().toCartesianThis( r, θ ); } let x = new Complex( 10, 5 ).sqrThis(); console.log( 'x = new Complex( 10, 5 ).sqrThis()' ); console.log( 'x is ', x ); let y = x.pow( 3 ); console.log ( 'y = x.pow( 3 )' ); console.log ( 'y is ', y ); x.sqr(); console.log ( 'x.sqr()' ); console.log ( 'x is still', x ); x.sqrThis(); console.log ( 'x.sqrThis()' ); console.log ( 'x is now', x );

簡而言之,以這種方式構建 class 提供了相同方法的兩個版本:

  • 一種體現 function 邏輯並直接改變實例this的 object 的方法。
  • 而另一種方法是簡單地克隆實例化的this object,然后調用包含 function 邏輯的關聯方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM