Reputation: 2915
I have written a small javascript program to view julia set images [link]. I've ben implementing multiple iteration algorithms (Not just z^2 + c
) and recently implemented exp(z)
. However, my output differs from the wikipedia page image. Here's a comparison for the c value -0.65
:
Obviously, their output is a lot more colourful and shows a lot more detail! I was thinking it is probably a mistake in my implementation, or maybe my coloring algorithm? Or is the wikipedia result generated by some differing means? Below is an MVE with just the e^z + c
algorithm and the relevant functions. If you want to play with the whole program feel free, it's hosted on my site.
//globals
var MAXITERATION = 2500;
var BOUNDARY = 4;
var CANVASID = "juliaDraw";
var CANVAS = document.getElementById("juliaDraw");
var CONTEXT = document.getElementById("juliaDraw").getContext('2d');
var HEIGHT = 750;
var WIDTH = 750;
var CONVERGENCEITERCOUNT = 2500;
/** Complex number functions **/
function complexNum(real, imaginary) {
this.real = real;
this.imaginary = imaginary;
return this;
}
// This is the bit that might be a problem but it seems to return correct results?
function raiseNumberToComplexPower(x, c) {
var s = Math.pow(x, c.real);
var pow = c.imaginary * Math.log(x);
var num = new complexNum(Math.cos(pow), Math.sin(pow));
return scalarComplex(s, num);
}
function addComplex(c1, c2) {
var real = c1.real + c2.real;
var imaginary = c1.imaginary + c2.imaginary;
return new complexNum(real, imaginary);
}
function multComplex(c1, c2) {
var real = (c1.real * c2.real) - (c1.imaginary * c2.imaginary);
var imaginary = (c1.real * c2.imaginary) + (c2.real * c1.imaginary);
return new complexNum(real, imaginary);
}
function scalarComplex(s, c) {
return new complexNum(c.real * s, c.imaginary * s);
}
function getComplexModulus(c) {
return Math.sqrt((c.real * c.real) + (c.imaginary * c.imaginary));
}
/** Drawing and manipulation **/
function createArray(length) {
var arr = new Array(length || 0),
i = length;
if (arguments.length > 1) {
var args = Array.prototype.slice.call(arguments, 1);
while (i--) arr[length - 1 - i] = createArray.apply(this, args);
}
return arr;
}
function drawJulia() {
CONTEXT.clearRect(0, 0, WIDTH, HEIGHT);
var start = new complexNum(-2, 2);
var c = new complexNum(readInput('realValue') * 1, readInput('imagValue') * 1);
STARTPOS = {
real: -2,
imaginary: 2
}
RANGE = 4;
plotJuliaSet(CANVASID, c);
}
function plotJuliaSet(canvasID, c) {
var complexNumberArray = createArray(WIDTH + 1, HEIGHT + 1);
var doesPointEscapeArray = createArray(WIDTH + 1, HEIGHT + 1);
ITERALGO = exponential;
for (var x = 0; x <= WIDTH; x++) {
for (var y = 0; y <= HEIGHT; y++) {
complexNumberArray[x][y] = new coordsToComplex({
x: x,
y: y
});
complexNumberArray[x][y] = complexNumberArray[x][y];
doesPointEscapeArray[x][y] = doesPointEscape(c, complexNumberArray[x][y]);
if (doesPointEscapeArray[x][y] >= 0) {
drawPointOnCanvas(x, y, getColor(doesPointEscapeArray[x][y]));
} else {
drawPointOnCanvas(x, y, 'black');
}
}
}
console.log('done');
}
function doesPointEscape(c, complexNum) {
var iterations = 0;
var iterationsToEscape = -1;
var escaped = false;
while ((!escaped) && (iterations < MAXITERATION)) {
if (getComplexModulus(complexNum) > BOUNDARY) {
escaped = true;
iterationsToEscape = iterations;
}
complexNum = ITERALGO(complexNum, c);
iterations++;
}
return iterationsToEscape;
}
function exponential(complexNum, c) {
// e^z + c
return addComplex(raiseNumberToComplexPower(Math.E, complexNum), c);
}
function drawPointOnCanvas(x, y, color) {
CONTEXT.fillStyle = color;
CONTEXT.fillRect(x, y, 1, 1);
}
function getColor(iterations) {
//console.log("Iterations: "+getBaseLog(iterations+1,255));
var color = "rgb(" + Math.floor((8 * iterations) % 255) + "," + Math.floor(2 * iterations % 255) + "," + Math.floor(255 - ((8 * iterations) % 255)) + ")";
//console.log(color);
return color;
}
function coordsToComplex(coordinates) {
return {
real: ((coordinates.x / WIDTH) * RANGE + STARTPOS.real),
imaginary: ((coordinates.y / HEIGHT) * -RANGE + STARTPOS.imaginary)
};
}
function complexToCoords(c) {
return {
x: ((c.real - STARTPOS.real) / (RANGE)) * WIDTH,
y: ((c.imaginary - STARTPOS.imaginary) / -(RANGE)) * HEIGHT
};
}
function readInput(inputID) {
return document.getElementById(inputID).value;
}
.desc {
float: right;
width: 300px;
}
#juliaDraw {
border: 1px dotted;
float: left;
}
.canvasWrapper canvas {
position: absolute;
top: 0;
left: 0;
}
<div class="desc">
<h1>Julia Set Viewer</h1>
<form>
<label>Real:
<input type="text" id="realValue" value="-0.65">
</label>
<br>
<label>Imag:
<input type="text" id="imagValue" value="0">
</label>
<input type="button" onClick="drawJulia()" value="Draw">
</form>
</div>
<canvas id="juliaDraw" width=750 height=750 onClick="drawZoomJulia()"></canvas>
Upvotes: 3
Views: 130
Reputation: 4964
The issue is really a mathematical one. Specifically, you're using an escape criterion built for polynomials on an exponential function. In all cases, it appears that you iterate until the iterate exceeds BOUNDARY
in absolute value and BOUNDARY
is set to 4 at the outset. The Wikepedia image evidently uses a much larger escape value. In the following two images, we compare an escape radius of 4 with an escape radius of 100; the larger escape radius is much more like the Wikipedia image:
But, frankly, the Wikipedia image is incorrect as well. The whole point behind Julia set pictures is to attempt to decompose the complex plane into two sets: one set where the dynamics are simple and another set where the dynamics are complicated. We iterate a polynomial until the absolute value is large because all points with large absolute value will escape to infinity. That's not true for your function exp(z)-0.65
. For example, if z=-100
, then abs(z)
is fairly large but exp(-100)-0.65
is very close to -0.65
. There's no good escape criterion in terms of the absolute value for exponential functions.
What does work well is to iterate your function until it's real part is large. It's like there's an escape to the right side of the complex plane. If we iterate your function until the real part exceeds 100 we get something like so:
Upvotes: 5