Fractals/Mathematics/Newton method
< Fractals < MathematicsIntroduction
types

Newton method can be used for finding successively better approximations to one root (zero) x of function f :
If one wants to find another root must apply the method again with other initial point
Newton method can be applied to :
- a real-valued function [3]
- a complex-valued function
- a system of equations
How to find all roots ? [4][5]
systems of nonlinear equations
One may also use Newton's method to solve systems of [6]
- 2 (non-linear) equations
- with 2 complex variables : [7]
It can be expressed more sinthetically using vector notation :
where is a vector of 2 complex variables :
and is a vector of functions ( or is a vector-valued function of the vector variable x ) :
Solution can be find by iteration :[8]
where s is an increment ( which is now a vector of 2 components ):
Increment can be computed by :
Here is Jacobian matrix[9] of at :
and is inverse of the Jacobian matrix .
In case of
- a small number of equations use the inverse matrix.
- a big number of equations : rather than actually computing the inverse of this matrix, one can save time by solving the system of linear equations for the unknown xn+1 − xn.
Algorithm in pseudocode : [10]
- start with an initial approximation ( guess)
- repeat until convergence ( iterate)
- solve:
- compute :
Stop criteria :
- absolute error : [11]
What is needed ?
- function f ( a differentiable function )
- it's derivative f'
- starting point x0 ( which should be in the basin of root's attraction )
- stopping rule ( criteria to stop iteration )[12]
stopping rule
Pitfalls
- method has a cycle and never converge (
- inflection point [13]
- local minimum
- method is diverging not converging
- local minimum
- multiple root ( multiplicity of root > 1 , for example : f(x) = f'(x) = 0). Slow approximation, derivative tends to zero, trouble in division step ( do not divide by zero ). Use modified Newton's method
- slow convergence[14]
- bad value of the initial approximation ( an initial guess far from the root )
- a function whose derivative vanishes near a root, or whose second derivative is unbounded near a root
- Function has inflection point ( method has a cycle and never converge)
- Local minimum
Pseudocode
The following is an example of using the Newton's Method to help find a root of a function f
which has derivative fprime
.
The initial guess will be and the function will be so that .
Each new iterative of Newton's method will be denoted by x1
. We will check during the computation whether the denominator (yprime
) becomes too small (smaller than epsilon
), which would be the case if , since otherwise a large amount of error could be introduced.
%These choices depend on the problem being solved
x0 = 1 %The initial value
f = @(x) x^2 - 2 %The function whose root we are trying to find
fprime = @(x) 2*x %The derivative of f(x)
tolerance = 10^(-7) %7 digit accuracy is desired
epsilon = 10^(-14) %Don't want to divide by a number smaller than this
maxIterations = 20 %Don't allow the iterations to continue indefinitely
haveWeFoundSolution = false %Were we able to find the solution to the desired tolerance? not yet.
for i = 1 : maxIterations
y = f(x0)
yprime = fprime(x0)
if(abs(yprime) < epsilon) %Don't want to divide by too small of a number
fprintf('WARNING: denominator is too small\n')
break; %Leave the loop
end
x1 = x0 - y/yprime %Do Newton's computation
if(abs(x1 - x0)/abs(x1) < tolerance) %If the result is within the desired tolerance
haveWeFoundSolution = true
break; %Done, so leave the loop
end
x0 = x1 %Update x0 to start the process again
end
if (haveWeFoundSolution) % We found a solution within tolerance and maximum number of iterations
fprintf('The root is: %f\n', x1);
else %If we weren't able to find a solution to within the desired tolerance
fprintf('Warning: Not able to find solution to within the desired tolerance of %f\n', tolerance);
fprintf('The last computed approximate root was %f\n', x1)
end
code
- c [15]
- matlab : newtonm function [16]
- Maxima CAS
- scilab [17]
- Mathematica[18]
applications of Newton's method in case of iterated complex quadratic polynomials
recurrence relations
Define the iterated function by:
Pick arbitrary names for the iterated function and it's derivatives These derivatives can be computed by recurrence relations.
arbitrary names | Initial conditions | Recurrence steps |
---|---|---|
Derivation of recurrence relations :
center
To use it in a GUI program :
- click on the menu item : nucleus
- using a mouse mark rectangle region around center of hyperbolic component
A center ( or nucleus ) of period satisfies :
Applying Newton's method in one variable:
where initial estimate is arbitrary choosen ( abs(n) <2.0 )
stoppping rule :
arbitrary names | Initial conditions | Recurrence steps |
---|---|---|
/*
code from unnamed c program ( book )
http://code.mathr.co.uk/book
see mandelbrot_nucleus.c
by Claude Heiland-Allen
COMPILE :
gcc -std=c99 -Wall -Wextra -pedantic -O3 -ggdb m.c -lm -lmpfr
usage:
./a.out bits cx cy period maxiters
output : space separated complex nucleus on stdout
example
./a.out 53 -1.75 0 3 100
./a.out 53 -1.75 0 30 100
./a.out 53 0.471 -0.3541 14 100
./a.out 53 -0.12 -0.74 3 100
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <gmp.h> // arbitrary precision
#include <mpfr.h>
extern int mandelbrot_nucleus(mpfr_t cx, mpfr_t cy, const mpfr_t c0x, const mpfr_t c0y, int period, int maxiters) {
int retval = 0;
mpfr_t zx, zy, dx, dy, s, t, u, v;
mpfr_inits2(mpfr_get_prec(c0x), zx, zy, dx, dy, s, t, u, v, (mpfr_ptr) 0);
mpfr_set(cx, c0x, GMP_RNDN);
mpfr_set(cy, c0y, GMP_RNDN);
for (int i = 0; i < maxiters; ++i) {
// z = 0
mpfr_set_ui(zx, 0, GMP_RNDN);
mpfr_set_ui(zy, 0, GMP_RNDN);
// d = 0
mpfr_set_ui(dx, 0, GMP_RNDN);
mpfr_set_ui(dy, 0, GMP_RNDN);
for (int p = 0; p < period; ++p) {
// d = 2 * z * d + 1;
mpfr_mul(u, zx, dx, GMP_RNDN);
mpfr_mul(v, zy, dy, GMP_RNDN);
mpfr_sub(u, u, v, GMP_RNDN);
mpfr_mul_2ui(u, u, 1, GMP_RNDN);
mpfr_mul(dx, dx, zy, GMP_RNDN);
mpfr_mul(dy, dy, zx, GMP_RNDN);
mpfr_add(dy, dx, dy, GMP_RNDN);
mpfr_mul_2ui(dy, dy, 1, GMP_RNDN);
mpfr_add_ui(dx, u, 1, GMP_RNDN);
// z = z^2 + c;
mpfr_sqr(u, zx, GMP_RNDN);
mpfr_sqr(v, zy, GMP_RNDN);
mpfr_mul(zy, zx, zy, GMP_RNDN);
mpfr_sub(zx, u, v, GMP_RNDN);
mpfr_mul_2ui(zy, zy, 1, GMP_RNDN);
mpfr_add(zx, zx, cx, GMP_RNDN);
mpfr_add(zy, zy, cy, GMP_RNDN);
}
// check d == 0
if (mpfr_zero_p(dx) && mpfr_zero_p(dy)) {
retval = 1;
goto done;
}
// st = c - z / d
mpfr_sqr(u, dx, GMP_RNDN);
mpfr_sqr(v, dy, GMP_RNDN);
mpfr_add(u, u, v, GMP_RNDN);
mpfr_mul(s, zx, dx, GMP_RNDN);
mpfr_mul(t, zy, dy, GMP_RNDN);
mpfr_add(v, s, t, GMP_RNDN);
mpfr_div(v, v, u, GMP_RNDN);
mpfr_mul(s, zy, dx, GMP_RNDN);
mpfr_mul(t, zx, dy, GMP_RNDN);
mpfr_sub(zy, s, t, GMP_RNDN);
mpfr_div(zy, zy, u, GMP_RNDN);
mpfr_sub(s, cx, v, GMP_RNDN);
mpfr_sub(t, cy, zy, GMP_RNDN);
// uv = st - c
mpfr_sub(u, s, cx, GMP_RNDN);
mpfr_sub(v, t, cy, GMP_RNDN);
// c = st
mpfr_set(cx, s, GMP_RNDN);
mpfr_set(cy, t, GMP_RNDN);
// check uv = 0
if (mpfr_zero_p(u) && mpfr_zero_p(v)) {
retval = 2;
goto done;
}
} // for (int i = 0; i < maxiters; ++i)
done: mpfr_clears(zx, zy, dx, dy, s, t, u, v, (mpfr_ptr) 0);
return retval;
}
void DescribeStop(int stop)
{
switch( stop )
{
case 0:
printf(" method stopped because i = maxiters\n");
break;
case 1:
printf(" method stopped because derivative == 0\n");
break;
//...
case 2:
printf(" method stopped because uv = 0\n");
break;
}
}
void usage(const char *progname) {
fprintf(stderr,
"program finds one center ( nucleus) of hyperbolic component of Mandelbrot set using Newton method"
"usage: %s bits cx cy period maxiters\n"
"outputs space separated complex nucleus on stdout\n"
"example %s 53 -1.75 0 3 100\n",
progname, progname);
}
int main(int argc, char **argv) {
// check the input
if (argc != 6) { usage(argv[0]); return 1; }
// read the values
int bits = atoi(argv[1]);
mpfr_t cx, cy, c0x, c0y;
mpfr_inits2(bits, cx, cy, c0x, c0y, (mpfr_ptr) 0);
mpfr_set_str(c0x, argv[2], 10, GMP_RNDN);
mpfr_set_str(c0y, argv[3], 10, GMP_RNDN);
int period = atoi(argv[4]);
int maxiters = atoi(argv[5]);
int stop;
//
stop = mandelbrot_nucleus(cx, cy, c0x, c0y, period, maxiters);
//
printf(" nucleus ( center) of component with period = %s near c = %s ; %s is : \n ", argv[4], argv[2], argv[3]);
mpfr_out_str(0, 10, 0, cx, GMP_RNDN);
putchar(' ');
mpfr_out_str(0, 10, 0, cy, GMP_RNDN);
putchar('\n');
//
DescribeStop(stop) ;
// clear memeory
mpfr_clears(cx, cy, c0x, c0y, (mpfr_ptr) 0);
//
return 0;
}
boundary
The boundary of the component with center of period can be parameterized by internal angles.
The boundary point at angle (measured in turns) satisfies system of 2 equations :
Defining a function of two complex variables:
and applying Newton's method in two variables:
where
This can be expressed using the recurrence relations as:
where are evaluated at .
Example
"The process is for numerically finding one boundary point on one component at a time - you can't solve for the whole boundary of all the components at once. So pick and fix one particular nucleus n and one particular internal angle t " (Claude Heiland-Allen [19])
Lets try find point of boundary ( bond point )
of hyperbolic component of Mandelbrot set for :
- period p=3
- angle t=0
- center ( nucleus ) c = n = -0.12256116687665+0.74486176661974i
There is only one such point.
Initial estimates (first guess) will be center ( nucleus ) of this components. This center ( complex number) will be saved in a vector of complex numbers containing two copies of that nucleus:
The boundary point at angle (measured in turns) satisfies system of 2 equations :
so :
Then function g
Jacobian matrix is
where denominator d is :
Then find better aproximation of point c=b using iteration :
using as an starting point :
Maxima CAS code

C++ code
/*
cpp code by Claude Heiland-Allen
http://mathr.co.uk/blog/
licensed GPLv3+
g++ -std=c++98 -Wall -pedantic -Wextra -O3 -o bonds bonds.cc
./bonds period nucleus_re nucleus_im internal_angle
./bonds 1 0 0 0
./bonds 1 0 0 0.5
./bonds 1 0 0 0.333333333333333
./bonds 2 -1 0 0
./bonds 2 -1 0 0.5
./bonds 2 -1 0 0.333333333333333
./bonds 3 -0.12256116687665 0.74486176661974 0
./bonds 3 -0.12256116687665 0.74486176661974 0.5
./bonds 3 -0.12256116687665 0.74486176661974 0.333333333333333
for b in $(seq -w 0 999)
do
./bonds 1 0 0 0.$b 2>/dev/null
done > period-1.txt
gnuplot
plot "./period-1.txt" with lines
-------------
for b in $(seq -w 0 999)
do
./bonds 3 -0.12256116687665 0.74486176661974 0.$b 2>/dev/null
done > period-3a.txt
gnuplot
plot "./period-3a.txt" with lines
*/
#include <complex>
#include <stdio.h>
#include <stdlib.h>
typedef std::complex<double> complex;
const double pi = 3.141592653589793;
const double eps = 1.0e-12;
struct param {
int period;
complex nucleus, bond;
};
struct recurrence {
complex A, B, C, D, E;
};
void recurrence_init(recurrence &r, const complex &z) {
r.A = z;
r.B = 1;
r.C = 0;
r.D = 0;
r.E = 0;
}
/*
void recurrence_step(recurrence &rr, const recurrence &r, const complex &c) {
rr.A = r.A * r.A + c;
rr.B = 2.0 * r.A * r.B;
rr.C = 2.0 * (r.B * r.B + r.A * r.C);
rr.D = 2.0 * r.A * r.D + 1.0;
rr.E = 2.0 * (r.A * r.E + r.B * r.D);
}
*/
void recurrence_step_inplace(recurrence &r, const complex &c) {
// this works because no component of r is read after it is written
r.E = 2.0 * (r.A * r.E + r.B * r.D);
r.D = 2.0 * r.A * r.D + 1.0;
r.C = 2.0 * (r.B * r.B + r.A * r.C);
r.B = 2.0 * r.A * r.B;
r.A = r.A * r.A + c;
}
struct matrix {
complex a, b, c, d;
};
struct vector {
complex u, v;
};
vector operator+(const vector &u, const vector &v) {
vector vv = { u.u + v.u, u.v + v.v };
return vv;
}
vector operator*(const matrix &m, const vector &v) {
vector vv = { m.a * v.u + m.b * v.v, m.c * v.u + m.d * v.v };
return vv;
}
matrix operator*(const complex &s, const matrix &m) {
matrix mm = { s * m.a, s * m.b, s * m.c, s * m.d };
return mm;
}
complex det(const matrix &m) {
return m.a * m.d - m.b * m.c;
}
matrix inv(const matrix &m) {
matrix mm = { m.d, -m.b, -m.c, m.a };
return (1.0/det(m)) * mm;
}
// newton stores <z, c> into vector as <u, v>
vector newton_init(const param &h) {
vector vv = { h.nucleus, h.nucleus };
return vv;
}
void print_equation(const matrix &m, const vector &v, const vector &k) {
fprintf(stderr, "/ % 20.16f%+20.16fi % 20.16f%+20.16fi \\ / z - % 20.16f%+20.16fi \\ = / % 20.16f%+20.16fi \\\n", real(m.a), imag(m.a), real(m.b), imag(m.b), real(v.u), imag(v.u), real(k.u), imag(k.u));
fprintf(stderr, "\\ % 20.16f%+20.16fi % 20.16f%+20.16fi / \\ c - % 20.16f%+20.16fi / \\ % 20.16f%+20.16fi /\n", real(m.c), imag(m.c), real(m.d), imag(m.d), real(v.v), imag(v.v), real(k.v), imag(k.v));
}
vector newton_step(const vector &v, const param &h) {
recurrence r;
recurrence_init(r, v.u);
for (int i = 0; i < h.period; ++i) {
recurrence_step_inplace(r, v.v);
}
// matrix equation: J * (vv - v) = -g
// solved by: vv = inv(J) * -g + v
// note: the C++ variable g has the value of semantic variable -g
matrix J = { r.B - 1.0, r.D, r.C, r.E };
vector g = { v.u - r.A, h.bond - r.B };
print_equation(J, v, g);
return inv(J) * g + v;
}
int main(int argc, char **argv) {
if (argc < 5) {
fprintf(stderr, "usage: %s period nucleus_re nucleus_im internal_angle\n", argv[0]);
return 1;
}
param h;
h.period = atoi(argv[1]);
h.nucleus = complex(atof(argv[2]), atof(argv[3]));
double angle = atof(argv[4]);
if (angle == 0 && h.period != 1) {
fprintf(stderr, "warning: possibly wrong results due to convergence into parent!\n");
}
h.bond = std::polar(1.0, 2.0 * pi * angle);
fprintf(stderr, "initial parameters:\n");
fprintf(stderr, "period = %d\n", h.period);
fprintf(stderr, "nucleus = % 20.16f%+20.16fi\n", real(h.nucleus), imag(h.nucleus));
fprintf(stderr, "bond = % 20.16f%+20.16fi\n", real(h.bond), imag(h.bond));
vector v = newton_init(h);
fprintf(stderr, "z =% 20.16f%+20.16fi\n", real(v.u), imag(v.u));
fprintf(stderr, "c =% 20.16f%+20.16fi\n", real(v.v), imag(v.v));
fprintf(stderr, "\n");
fprintf(stderr, "newton iteration:\n");
vector vv;
do {
vv = v;
v = newton_step(vv, h);
fprintf(stderr, "z =% 20.16f%+20.16fi\n", real(v.u), imag(v.u));
fprintf(stderr, "c =% 20.16f%+20.16fi\n", real(v.v), imag(v.v));
fprintf(stderr, "\n");
} while (abs(v.u - vv.u) + abs(v.v - vv.v) > eps);
fprintf(stderr, "bond location:\n");
fprintf(stderr, "c =% 20.16f%+20.16fi\n", real(v.v), imag(v.v));
// suitable for gnuplot
printf("%.16f %.16f\n", real(v.v), imag(v.v));
return 0;
}
size
Newton's method (providing the initial estimate is sufficiently good and the function is well-behaved) provides successively more accurate approximations. How to tell when the approximation is good enough? One way might be to compare the center with points on its boundary, and continue to increase precision until this distance is accurate to enough bits.
Algorithm:
- given a center location estimate accurate to bits of precision
- compute a boundary location estimate accurate to the same precision, using center as starting value
- compute to find an estimate of the size of the component
- if it isn't zero, and if it is accurate enough (to a few 10s of bits, perhaps) then finish
- otherwise increase precision, refine center estimate to new precision, try again from the top
Measuring effective accuracy of the distance between two very close points might be done by comparing floating point exponents with floating point mantissa precision.
internal coordinate
It is possible to map hyperbolic component to closed unit disk centered at origin :
.
This relation is described by system of 2 equations :
where
- p is the period of the target hyperbolic component on parameter plane
- the desired internal angle of points c and b
- is an internal radius
- is a point of unit circle = internal coordinate
- c is a point of parameter plane
- z is a point of dynamic plane
- and
The algorithm by Claude Heiland-Allen for finding internal coordinate b from c :
- choose c
- check c ( bailout test on dynamical plane). When c is outside the Mandelbrot set, give up now ( or compute external coordinate );
- Start with period one :
- while
- Find such that using Newton's method in one complex variable;
- Find b by evaluating at
- If then return b
- otherwise continue with the next p :
To solve such system of equations one can use Newton method.
internal ray

/*
http://code.mathr.co.uk/mandelbrot-graphics/blob/HEAD:/c/bin/m-subwake-diagram-a.c
by Claude Heiland-Allen
*/
#include <mandelbrot-graphics.h>
#include <mandelbrot-numerics.h>
#include <mandelbrot-symbolics.h>
#include <cairo.h>
const double twopi = 6.283185307179586;
void draw_internal_ray(m_image *image, m_d_transform *transform, int period, double _Complex nucleus, const char *angle, double pt, m_pixel_t colour) {
int steps = 128;
mpq_t theta;
mpq_init(theta);
mpq_set_str(theta, angle, 10);
mpq_canonicalize(theta);
double a = twopi * mpq_get_d(theta);
mpq_clear(theta);
double _Complex interior = cos(a) + I * sin(a);
double _Complex cl = 0, cl2 = 0;
double _Complex c = nucleus;
double _Complex z = c;
cairo_surface_t *surface = m_image_surface(image);
cairo_t *cr = cairo_create(surface);
cairo_set_source_rgba(cr, m_pixel_red(colour), m_pixel_green(colour), m_pixel_blue(colour), m_pixel_alpha(colour));
for (int i = 0; i < steps; ++i) {
if (2 * i == steps) {
cl = c;
}
if (2 * i == steps + 2) {
cl2 = c;
}
double radius = (i + 0.5) / steps;
m_d_interior(&z, &c, z, c, radius * interior, period, 64);
double _Complex pc = c;
double _Complex pdc = 1;
m_d_transform_reverse(transform, &pc, &pdc);
if (i == 0) {
cairo_move_to(cr, creal(pc), cimag(pc));
} else {
cairo_line_to(cr, creal(pc), cimag(pc));
}
}
cairo_stroke(cr);
if (a != 0) {
double t = carg(cl2 - cl);
cairo_save(cr);
double _Complex dcl = 1;
m_d_transform_reverse(transform, &cl, &dcl);
cairo_translate(cr, creal(cl), cimag(cl));
cairo_rotate(cr, -t);
cairo_translate(cr, 0, -pt/3);
cairo_select_font_face(cr, "LMSans10", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
cairo_set_font_size(cr, pt);
cairo_text_path(cr, angle);
cairo_fill(cr);
cairo_restore(cr);
}
cairo_destroy(cr);
}
external rays
Dynamic ray
// qmnplane.cpp by Wolf Jung (C) 2007-2014.
// part of Mandel 5.11 http://mndynamics.com/indexp.html
// the GNU General Public License
//Time ~ nmax^2 , therefore limited nmax .
int QmnPlane::newtonRay(int signtype, qulonglong N, qulonglong D,
double &a, double &b, int q, QColor color, int mode) //5, white, 1
{ uint n; int j, i, k, i0, k0; mndAngle t; t.setAngle(N, D, j);
double logR = 14.0, x, y, u;
u = exp(0.5*logR); y = t.radians();
x = u*cos(y); y = u*sin(y);
if (pointToPos(x, y, i0, k0) > 1) i0 = -10;
stop(); QPainter *p = new QPainter(buffer); p->setPen(color);
for (n = 1; n <= (nmax > 5000u ? 5000u : nmax + 2); n++)
{ t.twice();
for (j = 1; j <= q; j++)
{ if (mode & 4 ? tricornNewton(signtype, n, a, b, x, y,
exp(-j*0.69315/q)*logR, t.radians())
: rayNewton(signtype, n, a, b, x, y,
exp(-j*0.69315/q)*logR, t.radians()) )
{ n = (n <= 20 ? 65020u : 65010u); break; }
if (pointToPos(x, y, i, k) > 1) i = -10;
if (i0 > -10)
{ if (i > -10) { if (!(mode & 8)) p->drawLine(i0, k0, i, k); }
else { n = 65020u; break; }
}
i0 = i; k0 = k;
}
}
//if rayNewton fails after > 20 iterations, endpoint may be accepted
delete p; update(); if (n >= 65020u) return 2;
if (mode & 2) { a = x; b = y; }
if (n >= 65010u) return 1; else return 0;
} //newtonRay
int QmnPlane::rayNewton(int signtype, uint n, double a, double b,
double &x, double &y, double rlog, double ilog)
{ uint k, l; double fx, fy, px, py, u, v = 0.0, d = 1.0 + x*x + y*y, t0, t1;
for (k = 1; k <= 60; k++)
{ if (signtype > 0) { a = x; b = y; }
fx = cos(ilog); fy = sin(ilog);
t0 = exp(rlog)*fx - 0.5*exp(-rlog)*(a*fx + b*fy);
t1 = exp(rlog)*fy + 0.5*exp(-rlog)*(a*fy - b*fx);
fx = x; fy = y; px = 1.0; py = 0.0;
for (l = 1; l <= n; l++)
{ u = 2.0*(fx*px - fy*py); py = 2.0*(fx*py + fy*px);
px = u; if (signtype > 0) px++;
u = fx*fx; v = fy*fy; fy = 2.0*fx*fy + b; fx = u - v + a;
u += v; v = px*px + py*py; if (u + v > 1.0e100) return 1;
}
fx -= t0; fy -= t1; if (v < 1.0e-50) return 2;
u = (fx*px + fy*py)/v; v = (fx*py - fy*px)/v;
px = u*u + v*v; if (px > 9.0*d) return -1;
x -= u; y += v; d = px; if (px < 1.0e-28 && k >= 5) break;
}
return 0;
} //rayNewton
Parameter ray
double externalAngle(...) { ... return (std::atan2(cky,ckx)); }
"This gets you the angle in only double-precision, but using double precision floating point throughout it's possible to get the external angle in much higher precision - the trick is to collect bits from the binary representation of the angle as you cross each dwell band - whether the final iterate that escaped has a positive or negative imaginary part determines if the bit is 0 or 1 respectively, see binary decomposition colouring http://www.mrob.com/pub/muency/binarydecomposition.html .
This is orthogonal to using Newton's method - it should also work with your potential field particle simulation technique as long as you never skip more than one dwell band at a time.
The idea behind Newton's method is to gradually reduce your target potential keeping the angle fixed to find a target final-z value, then solve for c in f^n(0, c) = z-final using the current c-value as initial guess for Newton's method iterations. The amount you reduce the potential by is controlled by a parameter called "sharpness", essentially the number of steps to take within each dwell band - usually I use between 4 and 8. This means the number of steps taken is known in advance (sharpness * initial dwell) and usually you don't need many Newton's method iterations to converge at each step (around 2 - 5 was typical in some tests I did a while ago).
You can only decrease potential so much for a fixed n with a fixed escape radius, so eventaully you decrement n, which increases |z-final| and gives you a choice of two angles (the pre-images under angle doubling) for arg z-final, you pick the one which is closer to the n'th iterate of your current c value, and which one you pick also tells you another bit of the external angle).
The algorithm is described here, for the other direction (starting from an external angle and tracing inwards to the Mandelbrot set - it's simpler because there is only one image under angle-doubling instead of two possibilities for the inverse): http://www.math.nagoya-u.ac.jp/~kawahira/programs/mandel-exray.pdf
I have a few implementations, the currently maintained ones are:
external ray tracing inwards (double precision floating point, libgmp arbitrary precision rational for external angle) https://gitorious.org/mandelbrot/mandelbrot-numerics/source/3d55cfc99cc97decb0ba0d9c2fb271a504b8e504:c/lib/m_d_exray_in.c
external ray tracing outwards (double precision floating point, libgmp arbitrary precision rational for external angle) https://gitorious.org/mandelbrot/mandelbrot-numerics/source/3d55cfc99cc97decb0ba0d9c2fb271a504b8e504:c/lib/m_d_exray_out.c
external ray tracing inwards (libmpfr arbitrary precision floating point, libgmp arbitrary precision rational for external angle) https://gitorious.org/mandelbrot/mandelbrot-numerics/source/3d55cfc99cc97decb0ba0d9c2fb271a504b8e504:c/lib/m_r_exray_in.c " Claude Heiland-Allen on Fractal Forum[20]
Compute one point of the ray
What one needs to start :
- external angle of the ray on wants to draw. Angle is usually given in turns
- function P ( which approximates Boettcher mapping )
- it's derivative P'
- starting point c0
- stopping rule ( criteria to stop iteration )
arbitrary names | Initial conditions | first value | Recurrence steps |
---|---|---|---|
Newton map :
arbitrary names | Initial conditions | first value | Recurrence steps |
---|---|---|---|
Compute n points of the ray
// compute and send to the output 4*depth complex points ( pair of arbitrary precision numbers )
for (int i = 0; i < depth * 4; ++i) {
mandelbrot_external_ray_in_step(ray);
mandelbrot_external_ray_in_get(ray, cre, cim);
// send new c to output : (input of other program) or (text file )
mpfr_out_str(0, 10, 0, cre, GMP_RNDN); putchar(' ');
mpfr_out_str(0, 10, 0, cim, GMP_RNDN); putchar('\n');
}
Radial parameter r
Depth
Using fixed integer D ( depth ) : [21]
and fixed maximal value of radial parameter :
one can compute D points of ray using fomula :
which is :
When then and radius reaches enough close to the boundary of Mandelbrot set
/* Maxima CAS code Number of ray points = depth r = radial parametr : 1 < R^{1/{2^D}} <= r > er */ GiveRay( angle, depth):= block ( [ r, R: 65536], r:R, for d:1 thru depth do ( r:er^(1/(2^d)), print("d = ", d, " r = ", float(r)) ) )$ compile(all)$ /* --------------------------- */ GiveRay(1/3,25)$
Output :
(%i4) GiveRay(1/3,25) "d = "1" r = "256.0 "d = "2" r = "16.0 "d = "3" r = "4.0 "d = "4" r = "2.0 "d = "5" r = "1.414213562373095 "d = "6" r = "1.189207115002721 "d = "7" r = "1.090507732665258 "d = "8" r = "1.044273782427414 "d = "9" r = "1.021897148654117 "d = "10" r = "1.0108892860517 "d = "11" r = "1.005429901112803 "d = "12" r = "1.002711275050203 "d = "13" r = "1.001354719892108 "d = "14" r = "1.000677130693066 "d = "15" r = "1.000338508052682 "d = "16" r = "1.000169239705302 "d = "17" r = "1.000084616272694 "d = "18" r = "1.000042307241396 "d = "19" r = "1.000021153396965 "d = "20" r = "1.00001057664255 "d = "21" r = "1.000005288307292 "d = "22" r = "1.00000264415015 "d = "23" r = "1.000001322074201 "d = "24" r = "1.000000661036882 "d = "25" r = "1.000000330518386
Depth and sharpness
Using fixed integer S called sharpness :
and ...( to do )
one can compute S*D points of ray using fomula :
... ( to do)
Note that last point is the same as in depth method, but there are more points here ( S*D > D ) .
GiveRay( angle, depth, sharpness):= block ( [ r, R: 65536, m ], r:R, for d:1 thru depth do ( for s:1 thru sharpness do ( m: (d-1)*sharpness +s, r:R^(1/(2^(m/sharpness))), print("d = ", d, " ; s = ", s , "; r = ", float(r)) ) ) )$ compile(all)$ /* --------------------------- */ GiveRay(1/3,25, 2)$
Output :
(%i4) GiveRay(1/3,25,2) "d = "1" ; s = "1"; r = "2545.456152628088 "d = "1" ; s = "2"; r = "256.0 "d = "2" ; s = "1"; r = "50.45251383854013 "d = "2" ; s = "2"; r = "16.0 "d = "3" ; s = "1"; r = "7.10299330131601 "d = "3" ; s = "2"; r = "4.0 "d = "4" ; s = "1"; r = "2.665144142690224 "d = "4" ; s = "2"; r = "2.0 "d = "5" ; s = "1"; r = "1.632526919438152 "d = "5" ; s = "2"; r = "1.414213562373095 "d = "6" ; s = "1"; r = "1.277703768264832 "d = "6" ; s = "2"; r = "1.189207115002721 "d = "7" ; s = "1"; r = "1.13035559372475 "d = "7" ; s = "2"; r = "1.090507732665258 "d = "8" ; s = "1"; r = "1.063181825335982 "d = "8" ; s = "2"; r = "1.044273782427414 "d = "9" ; s = "1"; r = "1.031107087230023 "d = "9" ; s = "2"; r = "1.021897148654117 "d = "10" ; s = "1"; r = "1.015434432757735 "d = "10" ; s = "2"; r = "1.0108892860517 "d = "11" ; s = "1"; r = "1.007687666272509 "d = "11" ; s = "2"; r = "1.005429901112803 "d = "12" ; s = "1"; r = "1.003836473870375 "d = "12" ; s = "2"; r = "1.002711275050203 "d = "13" ; s = "1"; r = "1.001916400639482 "d = "13" ; s = "2"; r = "1.001354719892108 "d = "14" ; s = "1"; r = "1.000957741685173 "d = "14" ; s = "2"; r = "1.000677130693066 "d = "15" ; s = "1"; r = "1.000478756238819 "d = "15" ; s = "2"; r = "1.000338508052682 "d = "16" ; s = "1"; r = "1.000239349475324 "d = "16" ; s = "2"; r = "1.000169239705302 "d = "17" ; s = "1"; r = "1.000119667577497 "d = "17" ; s = "2"; r = "1.000084616272694 "d = "18" ; s = "1"; r = "1.000059831998815 "d = "18" ; s = "2"; r = "1.000042307241396 "d = "19" ; s = "1"; r = "1.000029915551937 "d = "19" ; s = "2"; r = "1.000021153396965 "d = "20" ; s = "1"; r = "1.000014957664103 "d = "20" ; s = "2"; r = "1.00001057664255 "d = "21" ; s = "1"; r = "1.000007478804085 "d = "21" ; s = "2"; r = "1.000005288307292 "d = "22" ; s = "1"; r = "1.000003739395051 "d = "22" ; s = "2"; r = "1.00000264415015 "d = "23" ; s = "1"; r = "1.000001869695778 "d = "23" ; s = "2"; r = "1.000001322074201 "d = "24" ; s = "1"; r = "1.000000934847452 "d = "24" ; s = "2"; r = "1.000000661036882 "d = "25" ; s = "1"; r = "1.000000467423617 "d = "25" ; s = "2"; r = "1.000000330518386
Src code
/*
it is a c console program which
- computes external parameter ray
- for complex quadratic polynomial fc(z) = z^2+c
- uses arbitrary precision ( mpfr) with dynamic precision adjustment
- uses Newton method ( described by T Kawahira) http://www.math.nagoya-u.ac.jp/~kawahira/programs/mandel-exray.pdf
gcc -o rayin e.c -std=c99 -Wall -Wextra -pedantic -lmpfr -lgmp -lm
./rayin 1/3 25
./rayin 1/3 25 >ray13.txt
./rayin 1/3 25 | ./rescale 53 53 -0.75 0 1.5 0 >rray13.txt
program uses the code by Claude Heiland-Allen
from https://gitorious.org/maximus/book/
" ray_in computes 4 points per dwell band, and ray_out currently computes 16.
Moreover ray_in has dynamic precision adjustment and
ray_out doesn't (so you need a high precision to get through the thin
gaps) - fixing both of these is on my mental todo list. ray_out will
always be slower though, as it needs to compute extra points when
crossing dwell bands to accumulate angle bits (and to work correctly)."
What algorithm do you use for drawing external ray ?
"essentially the Newton's method one in
http://www.math.nagoya-u.ac.jp/~kawahira/programs/mandel-exray.pdf
precision is automatic, effectively it checks how close dwell bands are
together and uses higher precision when they are narrow and lower
precision when they are wide. in general precision will increase along
the ray as it gets closer to the boundary. "
"periodic rays stop near the landing point at the current zoom level,
manually retrace if you zoom in and find the gap annoying. tracing
periodic rays is a lot slower than tracing preperiodic rays, especially
if you zoom in to the cusp - suggestions welcome for improvements here
(perhaps a larger radius than a single pixel for landing point nearness
threshold?). "
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <gmp.h>
#include <mpfr.h> // arbitrary precision
const float pif = 3.14159265358979323846264338327950288419716939937510f;
const double pi = 3.14159265358979323846264338327950288419716939937510;
const long double pil = 3.14159265358979323846264338327950288419716939937510l;
struct mandelbrot_external_ray_in {
mpq_t angle;
mpq_t one;
unsigned int sharpness; // number of steps to take within each dwell band
unsigned int precision; // delta needs this many bits of effective precision
unsigned int accuracy; // epsilon is scaled relative to magnitude of delta
double escape_radius;
mpfr_t epsilon2;
mpfr_t cx;
mpfr_t cy;
unsigned int j;
unsigned int k;
// temporaries
mpfr_t zx, zy, dzx, dzy, ux, uy, vx, vy, ccx, ccy, cccx, cccy;
};
// new : allocates memory, inits and sets variables
// input angle = external angle in turns
extern struct mandelbrot_external_ray_in *mandelbrot_external_ray_in_new(mpq_t angle) {
struct mandelbrot_external_ray_in *r = malloc(sizeof(struct mandelbrot_external_ray_in));
mpq_init(r->angle);
mpq_set(r->angle, angle);
mpq_init(r->one);
mpq_set_ui(r->one, 1, 1);
r->sharpness = 4;
r->precision = 4;
r->accuracy = 4;
r->escape_radius = 65536.0;
mpfr_init2(r->epsilon2, 53);
mpfr_set_ui(r->epsilon2, 1, GMP_RNDN);
// external angle in radions
double a = 2.0 * pi * mpq_get_d(r->angle);
// initial value c =
mpfr_init2(r->cx, 53);
mpfr_init2(r->cy, 53);
mpfr_set_d(r->cx, r->escape_radius * cos(a), GMP_RNDN);
mpfr_set_d(r->cy, r->escape_radius * sin(a), GMP_RNDN);
r->k = 0;
r->j = 0;
// initialize temporaries
mpfr_inits2(53, r->zx, r->zy, r->dzx, r->dzy, r->ux, r->uy, r->vx, r->vy, r->ccx, r->ccy, r->cccx, r->cccy, (mpfr_ptr) 0);
return r;
}
// delete
extern void mandelbrot_external_ray_in_delete(struct mandelbrot_external_ray_in *r) {
mpfr_clear(r->epsilon2);
mpfr_clear(r->cx);
mpfr_clear(r->cy);
mpq_clear(r->angle);
mpq_clear(r->one);
mpfr_clears(r->zx, r->zy, r->dzx, r->dzy, r->ux, r->uy, r->vx, r->vy, r->ccx, r->ccy, r->cccx, r->cccy, (mpfr_ptr) 0);
free(r);
}
// step
extern int mandelbrot_external_ray_in_step(struct mandelbrot_external_ray_in *r) {
if (r->j >= r->sharpness) {
mpq_mul_2exp(r->angle, r->angle, 1);
if (mpq_cmp_ui(r->angle, 1, 1) >= 0) {
mpq_sub(r->angle, r->angle, r->one);
}
r->k++;
r->j = 0;
}
// r0 <- er ** ((1/2) ** ((j + 0.5)/sharpness))
// t0 <- r0 cis(2 * pi * angle)
double r0 = pow(r->escape_radius, pow(0.5, (r->j + 0.5) / r->sharpness));
double a0 = 2.0 * pi * mpq_get_d(r->angle);
double t0x = r0 * cos(a0);
double t0y = r0 * sin(a0);
// c <- r->c
mpfr_set(r->ccx, r->cx, GMP_RNDN);
mpfr_set(r->ccy, r->cy, GMP_RNDN);
for (unsigned int i = 0; i < 64; ++i) { // FIXME arbitrary limit
// z <- 0
// dz <- 0
mpfr_set_ui(r->zx, 0, GMP_RNDN);
mpfr_set_ui(r->zy, 0, GMP_RNDN);
mpfr_set_ui(r->dzx, 0, GMP_RNDN);
mpfr_set_ui(r->dzy, 0, GMP_RNDN);
// iterate
for (unsigned int p = 0; p <= r->k; ++p) {
// dz <- 2 z dz + 1
mpfr_mul(r->ux, r->zx, r->dzx, GMP_RNDN);
mpfr_mul(r->uy, r->zy, r->dzy, GMP_RNDN);
mpfr_mul(r->vx, r->zx, r->dzy, GMP_RNDN);
mpfr_mul(r->vy, r->zy, r->dzx, GMP_RNDN);
mpfr_sub(r->dzx, r->ux, r->uy, GMP_RNDN);
mpfr_add(r->dzy, r->vx, r->vy, GMP_RNDN);
mpfr_mul_2ui(r->dzx, r->dzx, 1, GMP_RNDN);
mpfr_mul_2ui(r->dzy, r->dzy, 1, GMP_RNDN);
mpfr_add_ui(r->dzx, r->dzx, 1, GMP_RNDN);
// z <- z^2 + c
mpfr_sqr(r->ux, r->zx, GMP_RNDN);
mpfr_sqr(r->uy, r->zy, GMP_RNDN);
mpfr_sub(r->vx, r->ux, r->uy, GMP_RNDN);
mpfr_mul(r->vy, r->zx, r->zy, GMP_RNDN);
mpfr_mul_2ui(r->vy, r->vy, 1, GMP_RNDN);
mpfr_add(r->zx, r->vx, r->ccx, GMP_RNDN);
mpfr_add(r->zy, r->vy, r->ccy, GMP_RNDN);
}
// c' <- c - (z - t0) / dz
mpfr_sqr(r->ux, r->dzx, GMP_RNDN);
mpfr_sqr(r->uy, r->dzy, GMP_RNDN);
mpfr_add(r->vy, r->ux, r->uy, GMP_RNDN);
mpfr_sub_d(r->zx, r->zx, t0x, GMP_RNDN);
mpfr_sub_d(r->zy, r->zy, t0y, GMP_RNDN);
mpfr_mul(r->ux, r->zx, r->dzx, GMP_RNDN);
mpfr_mul(r->uy, r->zy, r->dzy, GMP_RNDN);
mpfr_add(r->vx, r->ux, r->uy, GMP_RNDN);
mpfr_div(r->ux, r->vx, r->vy, GMP_RNDN);
mpfr_sub(r->cccx, r->ccx, r->ux, GMP_RNDN);
mpfr_mul(r->ux, r->zy, r->dzx, GMP_RNDN);
mpfr_mul(r->uy, r->zx, r->dzy, GMP_RNDN);
mpfr_sub(r->vx, r->ux, r->uy, GMP_RNDN);
mpfr_div(r->uy, r->vx, r->vy, GMP_RNDN);
mpfr_sub(r->cccy, r->ccy, r->uy, GMP_RNDN);
// delta^2 = |c' - c|^2
mpfr_sub(r->ux, r->cccx, r->ccx, GMP_RNDN);
mpfr_sub(r->uy, r->cccy, r->ccy, GMP_RNDN);
mpfr_sqr(r->vx, r->ux, GMP_RNDN);
mpfr_sqr(r->vy, r->uy, GMP_RNDN);
mpfr_add(r->ux, r->vx, r->vy, GMP_RNDN);
// enough_bits = 0 < 2 * prec(eps^2) + exponent eps^2 - 2 * precision
int enough_bits = 0 < 2 * (mpfr_get_prec(r->epsilon2) - r->precision) + mpfr_get_exp(r->epsilon2);
if (enough_bits) {
// converged = delta^2 < eps^2
int converged = mpfr_less_p(r->ux, r->epsilon2);
if (converged) {
// eps^2 <- |c' - r->c|^2 >> (2 * accuracy)
mpfr_sub(r->ux, r->cccx, r->cx, GMP_RNDN);
mpfr_sub(r->uy, r->cccy, r->cy, GMP_RNDN);
mpfr_sqr(r->vx, r->ux, GMP_RNDN);
mpfr_sqr(r->vy, r->uy, GMP_RNDN);
mpfr_add(r->ux, r->vx, r->vy, GMP_RNDN);
mpfr_div_2ui(r->epsilon2, r->ux, 2 * r->accuracy, GMP_RNDN);
// j <- j + 1
r->j = r->j + 1;
// r->c <- c'
mpfr_set(r->cx, r->cccx, GMP_RNDN);
mpfr_set(r->cy, r->cccy, GMP_RNDN);
return 1;
}
} else {
// bump precision
mpfr_prec_t prec = mpfr_get_prec(r->cx) + 32;
mpfr_prec_round(r->cx, prec, GMP_RNDN);
mpfr_prec_round(r->cy, prec, GMP_RNDN);
mpfr_prec_round(r->epsilon2, prec, GMP_RNDN);
mpfr_set_prec(r->ccx, prec);
mpfr_set_prec(r->ccy, prec);
mpfr_set_prec(r->cccx, prec);
mpfr_set_prec(r->cccy, prec);
mpfr_set_prec(r->zx, prec);
mpfr_set_prec(r->zy, prec);
mpfr_set_prec(r->dzx, prec);
mpfr_set_prec(r->dzy, prec);
mpfr_set_prec(r->ux, prec);
mpfr_set_prec(r->uy, prec);
mpfr_set_prec(r->vx, prec);
mpfr_set_prec(r->vy, prec);
i = 0;
}
mpfr_set(r->ccx, r->cccx, GMP_RNDN);
mpfr_set(r->ccy, r->cccy, GMP_RNDN);
}
return 0;
}
// get
extern void mandelbrot_external_ray_in_get(struct mandelbrot_external_ray_in *r, mpfr_t x, mpfr_t y) {
mpfr_set_prec(x, mpfr_get_prec(r->cx));
mpfr_set(x, r->cx, GMP_RNDN);
mpfr_set_prec(y, mpfr_get_prec(r->cy));
mpfr_set(y, r->cy, GMP_RNDN);
}
void usage(const char *progname) {
fprintf(stderr,
"usage: %s angle depth\n"
"outputs space-separated complex numbers on stdout\n"
"example : \n %s 1/3 25\n"
"or \n %s 1/3 25 > r.txt\n",
progname, progname, progname);
}
// needs input
int main(int argc, char **argv) {
// read and check input
if (argc < 3) { usage(argv[0]); return 1; }
mpq_t angle;
mpq_init(angle);
mpq_set_str(angle, argv[1], 0);
mpq_canonicalize(angle);
int depth = atoi(argv[2]);
struct mandelbrot_external_ray_in *ray = mandelbrot_external_ray_in_new(angle);
mpfr_t cre, cim;
mpfr_init2(cre, 53);
mpfr_init2(cim, 53);
// compute and send output
for (int i = 0; i < depth * 4; ++i) {
mandelbrot_external_ray_in_step(ray);
mandelbrot_external_ray_in_get(ray, cre, cim);
// send new c to output
mpfr_out_str(0, 10, 0, cre, GMP_RNDN); putchar(' ');
mpfr_out_str(0, 10, 0, cim, GMP_RNDN); putchar('\n');
}
// clear
mpfr_clear(cre);
mpfr_clear(cim);
mandelbrot_external_ray_in_delete(ray);
mpq_clear(angle);
return 0;
}
See also
References
- ↑ NEWTON’S METHOD AND FRACTALS by AARON BURTON
- ↑ Newton, Chebyshev, and Halley Basins of Attraction; A Complete Geometric Approach by Bart D. Stewart
- ↑ Everything You Always Wanted to Ask About Newton's Method But Were Afraid to Know by Ernst W. Mayer
- ↑ "How to find all roots of complex polynomials by Newton's method", by Hubbard, Schliecher, and Sutherland
- ↑ Finding the all roots of a polynomial by using Newton-Raphson method.
- ↑ math.stackexchange question: how-to-solve-simultaneous-equations-using-newton-raphsons-method
- ↑ Iterative Methods for Sparse Linear Systems Luca Bergamaschi
- ↑ Newton's method for nonlinear systems by Mark S. Gockenbach 2003-01-23
- ↑ wikipedia : Jacobian_matrix_and_determinant
- ↑ Iterative Methods for Sparse Linear Systems Luca Bergamaschi
- ↑ Multiple Nonlinear Equations using the Newton-Raphson Method by Bruce A. Finlayson
- ↑ Stopping Criterion by Prof. Alan Hood
- ↑ Numerical Methods for Scientists and Engineers by Richard Hamming, page 69
- ↑ StackExchange : A function for which the Newton-Raphson method slowly converges?
- ↑ Numerical Recipies In C
- ↑ Numerical_Methods_in_Civil_Engineering : NonLinear Equations Matlab By Gilberto E. Urroz, September 2004
- ↑ Solving nonlinera equations with Scilab by G E Urroz
- ↑ Principles of Linear Algebra With Mathematica® The Newton–Raphson Method Kenneth Shiskowski and Karl Frinkle
- ↑ Home page of Claude Heiland-Allen
- ↑ Fractal Forum : smooth external angle of Mandelbrot set?
- ↑ Drawing external ray using Newton method ( described by T Kawahira)