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