package mandelbrot; import java.util.function.Function; /** * A class to compute how fast a parameterized polynomial sequence diverges. * This is used to compute the colors of point in the Mandelbrot fractal. */ public class Mandelbrot { /** * If a complex has modulus above <code>RADIUS</code>, we know that * the sequence diverges. <code>RADIUS</code> should be at least 2 for * the usual Mandelbrot sequence. */ private static final double RADIUS = 10; /** * The square of <code>RADIUS</code>, used in computations. */ private static final double RADIUS2 = RADIUS * RADIUS; /** * How many iterations of the sequence do we compute before concluding * that it probably converges. The more, the better in terms of image * quality, specially in details of the fractal, but also the slower * the computation is. */ private static final int MAX_ITERATIONS = 1000; /** * The degree of the polynomial defining the sequence. */ private static final int DEGREE = 2; /** * Compute how divergent is the sequence generated by <code>z -> z ** 2 + c</code> * * @param c A complex parameter, defining the polynomial to use. * @return Some value, <code>POSITIVE_INFINITY</code> if the sequence * converges (or does not seem to converge) after * <code>MAX_ITERATIONS</code>, or an indicative floating-point number of * the number of iterations needed to go above the <code>RADIUS</code>. */ public double divergence(Complex c) { if (isConvergent(c)) return Double.POSITIVE_INFINITY; Function<Complex, Complex> f = z -> z.pow(DEGREE).add(c); Sequence seq = new Sequence(c, f); int countIterations = 0; for (Complex z : seq) { if (isDiverging(z)) return smoothIterationCount(countIterations, z); if (countIterations >= MAX_ITERATIONS) return Double.POSITIVE_INFINITY; countIterations++; } return 0.; } /** * This method is used to smooth the number of iterations until * getting out of the <code>RADIUS</code>, so that we get a * floating-point value and thus smooth coloring. * * @param countIterations the iteration on which <code>RADIUS</code> is beaten. * @param z the first complex of the sequence whose modulus is above <code>RADIUS</code> * @return a double close to <code>countIterations</code>. */ private double smoothIterationCount(int countIterations, Complex z) { double x = Math.log(z.modulus()) / Math.log(RADIUS); return (double) countIterations - Math.log(x) / Math.log(DEGREE); } /** * Checks whether a term of the sequence is out of the given * <code>RADIUS</code>, which guarantees that the sequence diverges. * * @param z a term of the sequence * @return <code>true</code> if we are sure that the sequence diverges. */ private boolean isDiverging(Complex z) { return z.squaredModulus() > RADIUS2; } /** * Checks whether the parameter of the sequence is in some region * that guarantees that the sequence is convergent. This does not * capture all convergent parameters. * * @param c the parameter for the polynomial * @return <code>true</code> if we are sure that the sequence converges. */ private boolean isConvergent(Complex c) { return isIn2Bulb(c) || isInCardioid(c); } /* The cardioid black shape of the fractal */ private boolean isInCardioid(Complex z) { double m = z.squaredModulus(); return Helpers.doubleCompare(m * (8 * m - 3), 3. / 32. - z.getReal()) <= 0; } /* The main black disc of the fractal */ private boolean isIn2Bulb(Complex z) { Complex zMinusOne = z.subtract(new Complex(-1, 0)); return Helpers.doubleCompare(zMinusOne.squaredModulus(), 1. / 16.) < 0; } }