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