# HG changeset patch # User Christian Urban # Date 1709749225 0 # Node ID c4561fc667b7a58f48c85e1170ba6c932d6eb597 # Parent 1a51207780e6bafc59d460fdd2e386f03369bf2a test diff -r 1a51207780e6 -r c4561fc667b7 progs/mandelbrot.sc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/progs/mandelbrot.sc Wed Mar 06 18:20:25 2024 +0000 @@ -0,0 +1,193 @@ +// Mandelbrot pictures +//===================== +// +// see https://en.wikipedia.org/wiki/Mandelbrot_set +// +// needs to be called with +// +// scala -cp scala-parallel-collections_3-1.0.4.jar +// +// !! UPDATE: On my faster Mac-M1 machine the times +// !! are ca. 4 secs for the sequential version and +// !! around 0.7 secs for the par-version. + + +import java.awt.Color +import java.awt.Dimension +import java.awt.Graphics +import java.awt.Graphics2D +import java.awt.image.BufferedImage +import javax.swing.JFrame +import javax.swing.JPanel +import javax.swing.WindowConstants +import scala.language.implicitConversions +import scala.collection.parallel.CollectionConverters._ + +// complex numbers +case class Complex(val re: Double, val im: Double) { + // represents the complex number re + im * i + def +(that: Complex) = Complex(this.re + that.re, this.im + that.im) + def -(that: Complex) = Complex(this.re - that.re, this.im - that.im) + def *(that: Complex) = Complex(this.re * that.re - this.im * that.im, + this.re * that.im + that.re * this.im) + def *(that: Double) = Complex(this.re * that, this.im * that) + def abs() = Math.sqrt(this.re * this.re + this.im * this.im) +} + +// to allow the notation n + m * i +object i extends Complex(0, 1) + +// implicit conversion from Doubles to Complex +implicit def d2c(d: Double) : Complex = Complex(d, 0) + + +// some customn colours for the "sliding effect" +val colours = List( + new Color(66, 30, 15), new Color(25, 7, 26), + new Color(9, 1, 47), new Color(4, 4, 73), + new Color(0, 7, 100), new Color(12, 44, 138), + new Color(24, 82, 177), new Color(57, 125, 209), + new Color(134, 181, 229), new Color(211, 236, 248), + new Color(241, 233, 191), new Color(248, 201, 95), + new Color(255, 170, 0), new Color(204, 128, 0), + new Color(153, 87, 0), new Color(106, 52, 3)) + +// the viewer panel with an image canvas +class Viewer(width: Int, height: Int) extends JPanel { + val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) + + override def paintComponent(g: Graphics) = + g.asInstanceOf[Graphics2D].drawImage(canvas, null, null) + + override def getPreferredSize() = + new Dimension(width, height) + + def clearCanvas(color: Color) = { + for (x <- 0 to width - 1; y <- 0 to height - 1) + canvas.setRGB(x, y, color.getRGB()) + repaint() + } +} + +// initialising the viewer panel +def openViewer(width: Int, height: Int) : Viewer = { + val frame = new JFrame("XYPlane") + val viewer = new Viewer(width, height) + frame.add(viewer) + frame.pack() + frame.setVisible(true) + frame.setResizable(false) + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE) + viewer +} + +// some hardcoded parameters +val W = 900 // width +val H = 800 // height +val black = Color.black +val viewer = openViewer(W, H) + +// draw a pixel on the canvas +def pixel(x: Int, y: Int, color: Color) = + viewer.canvas.setRGB(x, y, color.getRGB()) + + +// calculates the number of iterations using lazy lists (streams) +// the iteration goes on for a maximum of max steps, +// but might leave early when the pred is satisfied +def iterations(c: Complex, max: Int) : Int = { + def next(z: Complex) = z * z + c + def pred(z: Complex) = z.abs() < 2 // exit condition + LazyList.iterate(0.0 * i, max)(next).takeWhile(pred).size +} + +// main function +// start and end are the upper-left and lower-right corners, +// max is the number of maximum iterations +def mandelbrot(start: Complex, end: Complex, max: Int) : Unit = { + viewer.clearCanvas(black) + + // deltas for each grid step + val d_x = (end.re - start.re) / W + val d_y = (end.im - start.im) / H + + for (y <- (0 until H).par) { + for (x <- (0 until W).par) { + + val c = start + + (x * d_x + y * d_y * i) + val iters = iterations(c, max) + val colour = + if (iters == max) black + else colours(iters % 16) + + pixel(x, y, colour) + } + viewer.updateUI() + } +} + + +// Examples +//========== + +//for measuring time +def time_needed[T](code: => T) = { + val start = System.nanoTime() + code + val end = System.nanoTime() + (end - start) / 1.0e9 +} + + + +// example 1 +val exa1 = -2.0 + -1.5 * i +val exa2 = 1.0 + 1.5 * i + +println(s"${time_needed(mandelbrot(exa1, exa2, 1000))} secs") + +// example 2 +val exb1 = -0.37465401 + 0.659227668 * i +val exb2 = -0.37332410 + 0.66020767 * i + +//time_needed(mandelbrot(exb1, exb2, 1000)) + +// example 3 +val exc1 = 0.435396403 + 0.367981352 * i +val exc2 = 0.451687191 + 0.380210061 * i + +//time_needed(mandelbrot(exc1, exc2, 1000)) + + + +// some more computations with example 3 + +val delta = (exc2 - exc1) * 0.0333 + +println(s"${time_needed( + for (n <- (0 to 25)) + mandelbrot(exc1 + delta * n, + exc2 - delta * n, 1000))} secs") + + + +// Larry Paulson's example +val exl1 = -0.74364990 + 0.13188170 * i +val exl2 = -0.74291189 + 0.13261971 * i + +//println(s"${time_needed(mandelbrot(exl1, exl2, 1000))} secs") + + +// example by Jorgen Villadsen +val exj1 = 0.10284 - 0.63275 * i +val exj2 = 0.11084 - 0.64075 * i + +//time_needed(mandelbrot(exj1, exj2, 1000)) + + +// another example +val exA = 0.3439274 + 0.6516478 * i +val exB = 0.3654477 + 0.6301795 * i + +//time_needed(mandelbrot(exA, exB, 1000))