updated
authorChristian Urban <christian.urban@kcl.ac.uk>
Fri, 26 Apr 2024 17:36:41 +0100
changeset 487 efad9725dfd8
parent 486 9c03b5e89a2a
child 488 5deaf53c2faa
updated
progs/mandelbrot.sc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/progs/mandelbrot.sc	Fri Apr 26 17:36:41 2024 +0100
@@ -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 12)) 
+//     mandelbrot(exc1 + delta * n, 
+//                exc2 - delta * n, 100))} 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))