diff --git a/src/demo/parallel/Complex.java b/src/demo/parallel/Complex.java
index 134a37946..dbf12396e 100644
--- a/src/demo/parallel/Complex.java
+++ b/src/demo/parallel/Complex.java
@@ -1,81 +1,23 @@
-/*
- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * - Neither the name of Oracle nor the names of its
- * contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
package demo.parallel;
-
-/**
- * A complex number is a number that can be expressed in the form a + b * i, where
- * a and b are real numbers and i is the imaginary unit, which satisfies the
- * equation i ^ 2 = -1. a is the real part and b is the imaginary part of the
- * complex number.
- *
- * This source code is provided to illustrate the usage of a given feature
- * or technique and has been deliberately simplified. Additional steps
- * required for a production-quality application, such as security checks,
- * input validation and proper error handling, might not be present in
- * this sample code.
- * @author Alexander Kouznetsov, Tristan Yan
+/*
+ * A complex number is a number that can be expressed in the form a + b * i.
*/
public class Complex {
-
- private double re; // the real part
- private double im; // the imaginary part
+ private double re; // real part
+ private double im; // imaginary part
- /**
- * create a new object with the given real and imaginary parts
- *
- * @param real a complex number real part
- * @param imag a complex number imaginary part
- */
public Complex(double real, double imag) {
re = real;
im = imag;
}
- /**
- * Add operation.
- * @param b summand
- * @return this Complex object whose value is (this + b)
- */
public Complex plus(Complex b) {
re += b.re;
im += b.im;
return this;
}
- /**
- * Multiply operation.
- * @param b multiplier
- * @return this Complex object whose value is this * b
- */
public Complex times(Complex b) {
Complex a = this;
double real = a.re * b.re - a.im * b.im;
@@ -85,12 +27,45 @@ public Complex times(Complex b) {
return this;
}
- /**
- * Square of Complex object's length, we're using square of length to
- * eliminate the computation of square root
- * @return square of length
- */
public double lengthSQ() {
return re * re + im * im;
}
+
+ // πΉ ΠΠΎΠ²ΡΠ΅ ΠΌΠ΅ΡΠΎΠ΄Ρ
+
+ /* ΠΡΡΠΈΡΠ°Π½ΠΈΠ΅ */
+ public Complex minus(Complex b) {
+ this.re -= b.re;
+ this.im -= b.im;
+ return this;
+ }
+
+ /* ΠΠ΅Π»Π΅Π½ΠΈΠ΅ */
+ public Complex dividedBy(Complex b) {
+ double den = b.re * b.re + b.im * b.im;
+ if (den == 0.0) throw new ArithmeticException("Division by zero");
+ double newRe = (this.re * b.re + this.im * b.im) / den;
+ double newIm = (this.im * b.re - this.re * b.im) / den;
+ this.re = newRe;
+ this.im = newIm;
+ return this;
+ }
+
+ /* Π‘ΠΎΠΏΡΡΠΆΠ΅Π½ΠΈΠ΅ */
+ public Complex conjugate() {
+ this.im = -this.im;
+ return this;
+ }
+
+ /* Π£ΠΌΠ½ΠΎΠΆΠ΅Π½ΠΈΠ΅ Π½Π° ΡΠΈΡΠ»ΠΎ */
+ public Complex scale(double k) {
+ this.re *= k;
+ this.im *= k;
+ return this;
+ }
+
+ /* ΠΠΎΠΏΠΈΡ ΠΎΠ±ΡΠ΅ΠΊΡΠ° (Π½ΡΠΆΠ½ΠΎ, ΡΡΠΎΠ±Ρ Π½Π΅ ΠΏΠΎΡΡΠΈΡΡ ΠΈΡΡ
ΠΎΠ΄Π½ΡΠΉ ΠΎΠ±ΡΠ΅ΠΊΡ ΠΏΡΠΈ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΡΡ
) */
+ public Complex copy() {
+ return new Complex(this.re, this.im);
+ }
}
\ No newline at end of file
diff --git a/src/demo/parallel/ComplexTest.java b/src/demo/parallel/ComplexTest.java
new file mode 100644
index 000000000..4b8676fe1
--- /dev/null
+++ b/src/demo/parallel/ComplexTest.java
@@ -0,0 +1,72 @@
+package demo.parallel;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * Unit-ΡΠ΅ΡΡΡ Π΄Π»Ρ Π½ΠΎΠ²ΡΡ
ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΉ ΠΊΠ»Π°ΡΡΠ° Complex.
+ */
+public class ComplexTest {
+
+ @Test
+ public void testMinus() {
+ Complex a = new Complex(3, 4);
+ Complex b = new Complex(1, -2);
+ a.minus(b); // (3+4i) - (1-2i) = (2+6i)
+ assertEquals(2.0, getRe(a), 1e-9);
+ assertEquals(6.0, getIm(a), 1e-9);
+ }
+
+ @Test
+ public void testDividedBy() {
+ Complex a = new Complex(3, 2);
+ Complex b = new Complex(1, -1);
+ a.dividedBy(b); // (3+2i) / (1-1i) = 0.5 + 2.5i
+ assertEquals(0.5, getRe(a), 1e-9);
+ assertEquals(2.5, getIm(a), 1e-9);
+ }
+
+ @Test(expected = ArithmeticException.class)
+ public void testDividedByZero() {
+ new Complex(1, 1).dividedBy(new Complex(0, 0));
+ }
+
+ @Test
+ public void testConjugate() {
+ Complex a = new Complex(3, -2);
+ a.conjugate(); // β (3+2i)
+ assertEquals(3.0, getRe(a), 1e-9);
+ assertEquals(2.0, getIm(a), 1e-9);
+ }
+
+ @Test
+ public void testScaleAndCopy() {
+ Complex a = new Complex(2, -3);
+ Complex b = a.copy().scale(2.5); // b = (5, -7.5), a Π½Π΅ ΠΈΠ·ΠΌΠ΅Π½ΠΈΠ»ΡΡ
+ assertEquals(2.0, getRe(a), 1e-9);
+ assertEquals(-3.0, getIm(a), 1e-9);
+ assertEquals(5.0, getRe(b), 1e-9);
+ assertEquals(-7.5, getIm(b), 1e-9);
+ }
+
+ // πΉ ΠΡΠΏΠΎΠΌΠΎΠ³Π°ΡΠ΅Π»ΡΠ½ΡΠ΅ ΠΌΠ΅ΡΠΎΠ΄Ρ (ΡΠ°ΠΊ ΠΊΠ°ΠΊ ΠΏΠΎΠ»Ρ Complex ΠΏΡΠΈΠ²Π°ΡΠ½ΡΠ΅)
+ private static double getRe(Complex z) {
+ try {
+ var f = Complex.class.getDeclaredField("re");
+ f.setAccessible(true);
+ return f.getDouble(z);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static double getIm(Complex z) {
+ try {
+ var f = Complex.class.getDeclaredField("im");
+ f.setAccessible(true);
+ return f.getDouble(z);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/demo/parallel/MandelbrotSetTask.java b/src/demo/parallel/MandelbrotSetTask.java
index adbb217b8..37a92e590 100644
--- a/src/demo/parallel/MandelbrotSetTask.java
+++ b/src/demo/parallel/MandelbrotSetTask.java
@@ -1,152 +1,32 @@
-/*
- * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * - Neither the name of Oracle nor the names of its
- * contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
- * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
package demo.parallel;
-
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
import javafx.concurrent.Task;
import javafx.scene.image.PixelWriter;
import javafx.scene.paint.Color;
-
-/**
- * Task to render Mandelbrot set using given parameters. See {@link
- * #MandelbrotRendererTask(boolean, javafx.scene.image.PixelWriter, int, int,
- * double, double, double, double, double, double, double, double, boolean)
- * constructor} for parameters list. The task returns time in milliseconds as
- * its calculated value.
- *
- *
- * This source code is provided to illustrate the usage of a given feature
- * or technique and has been deliberately simplified. Additional steps
- * required for a production-quality application, such as security checks,
- * input validation and proper error handling, might not be present in
- * this sample code.
- *
- * @author Alexander Kouznetsov, Tristan Yan
- */
class MandelbrotSetTask extends Task {
-
- /**
- * Calculation times, deliberately choose it as 256 because we will use the
- * count to calculate Color
- */
private static final int CAL_MAX_COUNT = 256;
-
- /**
- * This is the square of max radius, Mandelbrot set contained in the closed
- * disk of radius 2 around the origin plus some area around, so
- * LENGTH_BOUNDARY is 6.
- */
private static final double LENGTH_BOUNDARY = 6d;
-
- /**
- * For antialiasing we break each pixel into 3x3 grid and interpolate
- * between values calculated on those grid positions
- */
private static final int ANTIALIASING_BASE = 3;
-
- /**
- * Sequential vs. parallel calculation mode
- */
+
private final boolean parallel;
-
- /**
- * Antialiased mode flag
- */
private final boolean antialiased;
-
- /**
- * Dimension of the area
- */
private final int width, height;
-
- /**
- * Rectangle range to exclude from calculations. Used to skip calculations
- * for parts of MandelbrotSet that are already calculated.
- */
private final double minX, minY, maxX, maxY;
-
- /**
- * Real and imaginary part of min and max number in the set we need
- * calculate
- */
private final double minR, minI, maxR, maxI;
-
- /**
- * Pixel writer to use for writing calculated pixels
- */
private final PixelWriter pixelWriter;
-
- /**
- * Flag indicating that some new pixels were calculated
- */
private volatile boolean hasUpdates;
-
- /**
- * Start time of the task in milliseconds
- */
private volatile long startTime = -1;
-
- /**
- * Total time of the task in milliseconds
- */
private volatile long taskTime = -1;
-
- /**
- * Progress of the task
- */
private final AtomicInteger progress = new AtomicInteger(0);
- /**
- * Creates a task to render a MandelBrot set into an image using given
- * PixelWriter with given dimensions of the image, given real and imaginary
- * values range and given rectangular area to skip. Also there is a switch
- * that disables more computational-extensive antialiasing mode.
- * @param parallel parallel vs. sequential switch
- * @param pixelWriter target to write pixels to
- * @param width width of the image area
- * @param height height of the image area
- * @param minR min real value of the area
- * @param minI min imaginary value of the area
- * @param maxR max real value of the area
- * @param maxI max imaginary value of the area
- * @param minX min x value of the rectangular area to skip
- * @param minY min y value of the rectangular area to skip
- * @param maxX max x value of the rectangular area to skip
- * @param maxY max y value of the rectangular area to skip
- * @param fast fast mode disables antialiasing
- */
- public MandelbrotSetTask(boolean parallel, PixelWriter pixelWriter, int width, int height, double minR, double minI, double maxR, double maxI, double minX, double minY, double maxX, double maxY, boolean fast) {
+ public MandelbrotSetTask(boolean parallel, PixelWriter pixelWriter,
+ int width, int height,
+ double minR, double minI, double maxR, double maxI,
+ double minX, double minY, double maxX, double maxY,
+ boolean fast) {
this.parallel = parallel;
this.pixelWriter = pixelWriter;
this.width = width;
@@ -163,59 +43,25 @@ public MandelbrotSetTask(boolean parallel, PixelWriter pixelWriter, int width, i
updateProgress(0, 0);
}
- /**
- *
- * @return whether new pixels were written to the image
- */
- public boolean hasUpdates() {
- return hasUpdates;
- }
-
- /**
- * @return true if task is parallel
- */
- public boolean isParallel() {
- return parallel;
- }
-
- /**
- * Clears the updates flag
- */
- public void clearHasUpdates() {
- hasUpdates = false;
- }
+ public boolean hasUpdates() { return hasUpdates; }
+ public boolean isParallel() { return parallel; }
+ public void clearHasUpdates() { hasUpdates = false; }
- /**
- * {@inheritDoc}
- */
@Override
protected void failed() {
super.failed();
getException().printStackTrace(System.err);
}
- /**
- * Returns current task execution time while task is running and total
- * task time when task is finished
- * @return task time in milliseconds
- */
public long getTime() {
- if (taskTime != -1) {
- return taskTime;
- }
- if (startTime == -1) {
- return 0;
- }
+ if (taskTime != -1) return taskTime;
+ if (startTime == -1) return 0;
return System.currentTimeMillis() - startTime;
}
- /**
- * {@inheritDoc}
- */
@Override
protected Long call() throws Exception {
synchronized(pixelWriter) {
- // Prepares an image
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
pixelWriter.setColor(x, y, Color.TRANSPARENT);
@@ -223,36 +69,18 @@ protected Long call() throws Exception {
}
}
startTime = System.currentTimeMillis();
-
- // We do horizontal lines in parallel when asked
+
IntStream yStream = IntStream.range(0, height);
- if (parallel) {
- yStream = yStream.parallel();
- } else {
- yStream = yStream.sequential();
- }
+ if (parallel) yStream = yStream.parallel();
+ else yStream = yStream.sequential();
+
updateProgress(0, height);
yStream.forEach((int y) -> {
-
- // We do pixels in horizontal lines always sequentially
for (int x = 0; x < width; x++) {
-
- // Skip excluded rectangular area
- if (!(x >= maxX || x < minX || y >= maxY || y < minY)) {
- continue;
- }
- Color c;
- if (antialiased) {
- c = calcAntialiasedPixel(x, y);
- } else {
- c = calcPixel(x, y);
- }
- if (isCancelled()) {
- return;
- }
- synchronized(pixelWriter) {
- pixelWriter.setColor(x, y, c);
- }
+ if (!(x >= maxX ||x < minX || y >= maxY || y < minY)) continue;
+ Color c = antialiased ? calcAntialiasedPixel(x, y) : calcPixel(x, y);
+ if (isCancelled()) return;
+ synchronized(pixelWriter) { pixelWriter.setColor(x, y, c); }
hasUpdates = true;
}
updateProgress(progress.incrementAndGet(), height);
@@ -261,55 +89,39 @@ protected Long call() throws Exception {
return taskTime;
}
- /**
- * Calculates number of iterations a complex quadratic polynomials
- * stays within a disk of some finite radius for a given complex number.
- *
- * This number is used to choose a color for this pixel for precalculated
- * color tables.
- *
- * @param comp a complex number used for calculation
- * @return number of iterations a value stayed within a given disk.
- */
private int calc(Complex comp) {
int count = 0;
- Complex c = new Complex(0, 0);
+ Complex z = new Complex(0, 0);
+ final Complex ONE = new Complex(1, 0);
+
do {
- c = c.times(c).plus(comp);
+ // num = z^2 + c
+ Complex num = z.copy().times(z).plus(comp);
+ // den = conj(z) + 1
+ Complex den = z.copy().conjugate().plus(ONE);
+ // Π½ΠΎΠ²Π°Ρ ΡΠΎΡΠΌΡΠ»Π°: z = (z^2 + c) / (conj(z) + 1)
+ z = num.dividedBy(den);
+
count++;
- } while (count < CAL_MAX_COUNT && c.lengthSQ() < LENGTH_BOUNDARY);
+ } while (count < CAL_MAX_COUNT && z.lengthSQ() < LENGTH_BOUNDARY);
+
return count;
}
- /**
- * Calculates a color of a given pixel on the image using
- * {@link #calc(demo.parallel.Complex) } method.
- * @param x x coordinate of the pixel in the image
- * @param y y coordinate of the pixel in the image
- * @return calculated color of the pixel
- */
private Color calcPixel(double x, double y) {
double re = (minR * (width - x) + x * maxR) / width;
double im = (minI * (height - y) + y * maxI) / height;
Complex calPixel = new Complex(re, im);
return getColor(calc(calPixel));
}
-
- /**
- * Calculates antialised color of a given pixel on the image by dividing
- * real and imaginary value ranges of a pixel by {@link #ANTIALIASING_BASE}
- * and doing interpolation between calculated values
- * @param x x coordinate of the pixel in the image
- * @param y y coordinate of the pixel in the image
- * @return calculated color of the pixel
- */
private Color calcAntialiasedPixel(int x, int y) {
double step = 1d / ANTIALIASING_BASE;
double N = ANTIALIASING_BASE * ANTIALIASING_BASE;
double r = 0, g = 0, b = 0;
for (int i = 0; i < ANTIALIASING_BASE; i++) {
for (int j = 0; j < ANTIALIASING_BASE; j++) {
- Color c = calcPixel(x + step * (i + 0.5) - 0.5, y + step * (j + 0.5) - 0.5);
+ Color c = calcPixel(x + step * (i + 0.5) - 0.5,
+ y + step * (j + 0.5) - 0.5);
r += c.getRed() / N;
g += c.getGreen() / N;
b += c.getBlue() / N;
@@ -318,65 +130,33 @@ private Color calcAntialiasedPixel(int x, int y) {
return new Color(clamp(r), clamp(g), clamp(b), 1);
}
- /**
- * Clamps the value in 0..1 interval
- * @param val value to clamp
- * @return value in 0..1 interval
- */
private double clamp(double val) {
return val > 1 ? 1 : val < 0 ? 0 : val;
}
- /**
- * Returns a color for a given iteration count.
- * @param count number of iterations return by
- * {@link #calc(demo.parallel.Complex)} method
- * @return color from pre-calculated table
- */
private Color getColor(int count) {
- if (count >= colors.length) {
- return Color.BLACK;
- }
+ if (count >= colors.length) return Color.BLACK;
return colors[count];
}
-
- /**
- * Pre-calculated colors table
- */
+
static final Color[] colors = new Color[256];
static {
-
- /**
- * Color stops for colors table: color values
- */
Color[] cc = {
- Color.rgb(40, 0, 0),
- Color.RED,
- Color.WHITE,
- Color.RED,
- Color.rgb(100, 0, 0),
- Color.RED,
- Color.rgb(50, 0, 0)
+ Color.rgb(0, 7, 100),
+ Color.BLUE,
+ Color.CYAN,
+ Color.YELLOW,
+ Color.WHITE
};
-
- /**
- * Color stops for colors table: relative position in the table
- */
- double[] cp = {
- 0, 0.17, 0.25, 0.30, 0.5, 0.75, 1,};
-
- /**
- * Color table population
- */
+ double[] cp = { 0.0, 0.25, 0.5, 0.75, 1.0 };
+
int j = 0;
for (int i = 0; i < colors.length; i++) {
double p = (double) i / (colors.length - 1);
- if (p > cp[j + 1]) {
- j++;
- }
+ if (p > cp[j + 1]) j++;
double val = (p - cp[j]) / (cp[j + 1] - cp[j]);
colors[i] = cc[j].interpolate(cc[j + 1], val);
}
}
-}
+}
\ No newline at end of file