| author | Christian Urban <christian.urban@kcl.ac.uk> | 
| Sat, 11 Mar 2023 22:42:09 +0000 | |
| changeset 461 | eda26fa6d3ec | 
| parent 441 | 5266495f4aad | 
| child 467 | 1b879b3e704e | 
| permissions | -rw-r--r-- | 
| 187 | 1 | // Mandelbrot pictures | 
| 309 | 2 | //===================== | 
| 266 | 3 | // | 
| 187 | 4 | // see https://en.wikipedia.org/wiki/Mandelbrot_set | 
| 266 | 5 | // | 
| 461 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 6 | // under scala 3.2.2 needs to be called with | 
| 309 | 7 | // | 
| 461 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 8 | // scala -cp scala-parallel-collections_3-1.0.4.jar | 
| 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 9 | // | 
| 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 10 | // !! UPDATE: On my faster Mac-M1 machine the times | 
| 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 11 | // !! are ca. 4 secs for the sequential version and | 
| 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 12 | // !! around 0.7 secs for the par-version. | 
| 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 13 | |
| 187 | 14 | |
| 124 | 15 | import java.awt.Color | 
| 16 | import java.awt.Dimension | |
| 17 | import java.awt.Graphics | |
| 18 | import java.awt.Graphics2D | |
| 19 | import java.awt.image.BufferedImage | |
| 20 | import javax.swing.JFrame | |
| 21 | import javax.swing.JPanel | |
| 22 | import javax.swing.WindowConstants | |
| 186 | 23 | import scala.language.implicitConversions | 
| 266 | 24 | import scala.collection.parallel.CollectionConverters._ | 
| 265 | 25 | |
| 124 | 26 | // complex numbers | 
| 186 | 27 | case class Complex(val re: Double, val im: Double) { 
 | 
| 28 | // represents the complex number re + im * i | |
| 29 | def +(that: Complex) = Complex(this.re + that.re, this.im + that.im) | |
| 30 | def -(that: Complex) = Complex(this.re - that.re, this.im - that.im) | |
| 31 | def *(that: Complex) = Complex(this.re * that.re - this.im * that.im, | |
| 32 | this.re * that.im + that.re * this.im) | |
| 33 | def *(that: Double) = Complex(this.re * that, this.im * that) | |
| 34 | def abs() = Math.sqrt(this.re * this.re + this.im * this.im) | |
| 124 | 35 | } | 
| 36 | ||
| 187 | 37 | // to allow the notation n + m * i | 
| 186 | 38 | object i extends Complex(0, 1) | 
| 461 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 39 | |
| 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 40 | // implicit conversion from Doubles to Complex | 
| 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 41 | given Conversion[Double, Complex] = Complex(_, 0) | 
| 186 | 42 | |
| 43 | ||
| 44 | // some customn colours for the "sliding effect" | |
| 124 | 45 | val colours = List( | 
| 186 | 46 | new Color(66, 30, 15), new Color(25, 7, 26), | 
| 47 | new Color(9, 1, 47), new Color(4, 4, 73), | |
| 48 | new Color(0, 7, 100), new Color(12, 44, 138), | |
| 49 | new Color(24, 82, 177), new Color(57, 125, 209), | |
| 50 | new Color(134, 181, 229), new Color(211, 236, 248), | |
| 51 | new Color(241, 233, 191), new Color(248, 201, 95), | |
| 52 | new Color(255, 170, 0), new Color(204, 128, 0), | |
| 53 | new Color(153, 87, 0), new Color(106, 52, 3)) | |
| 124 | 54 | |
| 266 | 55 | // the viewer panel with an image canvas | 
| 124 | 56 | class Viewer(width: Int, height: Int) extends JPanel {
 | 
| 186 | 57 | val canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) | 
| 58 | ||
| 59 | override def paintComponent(g: Graphics) = | |
| 60 | g.asInstanceOf[Graphics2D].drawImage(canvas, null, null) | |
| 61 | ||
| 62 | override def getPreferredSize() = | |
| 63 | new Dimension(width, height) | |
| 124 | 64 | |
| 186 | 65 |   def clearCanvas(color: Color) = {
 | 
| 66 | for (x <- 0 to width - 1; y <- 0 to height - 1) | |
| 67 | canvas.setRGB(x, y, color.getRGB()) | |
| 68 | repaint() | |
| 69 | } | |
| 124 | 70 | } | 
| 71 | ||
| 266 | 72 | // initialising the viewer panel | 
| 186 | 73 | def openViewer(width: Int, height: Int) : Viewer = {
 | 
| 74 |   val frame = new JFrame("XYPlane")
 | |
| 75 | val viewer = new Viewer(width, height) | |
| 76 | frame.add(viewer) | |
| 77 | frame.pack() | |
| 78 | frame.setVisible(true) | |
| 79 | frame.setResizable(false) | |
| 80 | frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE) | |
| 81 | viewer | |
| 124 | 82 | } | 
| 83 | ||
| 266 | 84 | // some hardcoded parameters | 
| 186 | 85 | val W = 900 // width | 
| 86 | val H = 800 // height | |
| 124 | 87 | val black = Color.black | 
| 88 | val viewer = openViewer(W, H) | |
| 89 | ||
| 266 | 90 | // draw a pixel on the canvas | 
| 124 | 91 | def pixel(x: Int, y: Int, color: Color) = | 
| 92 | viewer.canvas.setRGB(x, y, color.getRGB()) | |
| 186 | 93 | |
| 124 | 94 | |
| 266 | 95 | // calculates the number of iterations using lazy lists (streams) | 
| 186 | 96 | // the iteration goes on for a maximum of max steps, | 
| 97 | // but might leave early when the pred is satisfied | |
| 98 | def iterations(c: Complex, max: Int) : Int = {
 | |
| 99 | def next(z: Complex) = z * z + c | |
| 353 | 100 | def pred(z: Complex) = z.abs() < 2 // exit condition | 
| 266 | 101 | LazyList.iterate(0.0 * i, max)(next).takeWhile(pred).size | 
| 186 | 102 | } | 
| 103 | ||
| 104 | // main function | |
| 266 | 105 | // start and end are the upper-left and lower-right corners, | 
| 187 | 106 | // max is the number of maximum iterations | 
| 186 | 107 | def mandelbrot(start: Complex, end: Complex, max: Int) : Unit = {
 | 
| 124 | 108 | viewer.clearCanvas(black) | 
| 186 | 109 | |
| 110 | // deltas for each grid step | |
| 111 | val d_x = (end.re - start.re) / W | |
| 112 | val d_y = (end.im - start.im) / H | |
| 124 | 113 | |
| 461 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 114 |   for (y <- (0 until H)) {
 | 
| 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 115 |     for (x <- (0 until W)) {
 | 
| 124 | 116 | |
| 186 | 117 | val c = start + | 
| 118 | (x * d_x + y * d_y * i) | |
| 119 | val iters = iterations(c, max) | |
| 461 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 120 | val colour = | 
| 186 | 121 | if (iters == max) black | 
| 122 | else colours(iters % 16) | |
| 124 | 123 | |
| 461 
eda26fa6d3ec
updated to scala 3
 Christian Urban <christian.urban@kcl.ac.uk> parents: 
441diff
changeset | 124 | pixel(x, y, colour) | 
| 143 | 125 | } | 
| 126 | viewer.updateUI() | |
| 186 | 127 | } | 
| 124 | 128 | } | 
| 129 | ||
| 187 | 130 | |
| 124 | 131 | // Examples | 
| 132 | //========== | |
| 133 | ||
| 134 | //for measuring time | |
| 135 | def time_needed[T](code: => T) = {
 | |
| 136 | val start = System.nanoTime() | |
| 137 | code | |
| 138 | val end = System.nanoTime() | |
| 139 | (end - start) / 1.0e9 | |
| 140 | } | |
| 141 | ||
| 142 | ||
| 353 | 143 | |
| 124 | 144 | // example 1 | 
| 186 | 145 | val exa1 = -2.0 + -1.5 * i | 
| 146 | val exa2 = 1.0 + 1.5 * i | |
| 124 | 147 | |
| 309 | 148 | println(s"${time_needed(mandelbrot(exa1, exa2, 1000))} secs")
 | 
| 124 | 149 | |
| 136 | 150 | // example 2 | 
| 186 | 151 | val exb1 = -0.37465401 + 0.659227668 * i | 
| 152 | val exb2 = -0.37332410 + 0.66020767 * i | |
| 124 | 153 | |
| 186 | 154 | //time_needed(mandelbrot(exb1, exb2, 1000)) | 
| 124 | 155 | |
| 156 | // example 3 | |
| 186 | 157 | val exc1 = 0.435396403 + 0.367981352 * i | 
| 158 | val exc2 = 0.451687191 + 0.380210061 * i | |
| 124 | 159 | |
| 166 | 160 | //time_needed(mandelbrot(exc1, exc2, 1000)) | 
| 124 | 161 | |
| 266 | 162 | |
| 163 | ||
| 124 | 164 | // some more computations with example 3 | 
| 186 | 165 | |
| 124 | 166 | val delta = (exc2 - exc1) * 0.0333 | 
| 167 | ||
| 394 | 168 | //println(s"${time_needed(
 | 
| 169 | // for (n <- (0 to 12)) | |
| 170 | // mandelbrot(exc1 + delta * n, | |
| 171 | // exc2 - delta * n, 100))} secs") | |
| 266 | 172 | |
| 124 | 173 | |
| 174 | ||
| 191 | 175 | // Larry Paulson's example | 
| 176 | val exl1 = -0.74364990 + 0.13188170 * i | |
| 177 | val exl2 = -0.74291189 + 0.13261971 * i | |
| 189 | 178 | |
| 309 | 179 | //println(s"${time_needed(mandelbrot(exl1, exl2, 1000))} secs")
 | 
| 189 | 180 | |
| 191 | 181 | |
| 182 | // example by Jorgen Villadsen | |
| 183 | val exj1 = 0.10284 - 0.63275 * i | |
| 184 | val exj2 = 0.11084 - 0.64075 * i | |
| 185 | ||
| 195 | 186 | //time_needed(mandelbrot(exj1, exj2, 1000)) | 
| 394 | 187 | |
| 188 | ||
| 189 | // another example | |
| 190 | val exA = 0.3439274 + 0.6516478 * i | |
| 191 | val exB = 0.3654477 + 0.6301795 * i | |
| 192 | ||
| 193 | //time_needed(mandelbrot(exA, exB, 1000)) |