| author | Christian Urban <christian.urban@kcl.ac.uk> | 
| Mon, 06 Nov 2023 21:49:55 +0000 | |
| changeset 477 | 2403c931a32f | 
| 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: 
441 
diff
changeset
 | 
11  | 
//  | 
| 
 
eda26fa6d3ec
updated to scala 3
 
Christian Urban <christian.urban@kcl.ac.uk> 
parents: 
441 
diff
changeset
 | 
12  | 
// !! UPDATE: On my faster Mac-M1 machine the times  | 
| 
 
eda26fa6d3ec
updated to scala 3
 
Christian Urban <christian.urban@kcl.ac.uk> 
parents: 
441 
diff
changeset
 | 
13  | 
// !! are ca. 4 secs for the sequential version and  | 
| 
 
eda26fa6d3ec
updated to scala 3
 
Christian Urban <christian.urban@kcl.ac.uk> 
parents: 
441 
diff
changeset
 | 
14  | 
// !! around 0.7 secs for the par-version.  | 
| 
 
eda26fa6d3ec
updated to scala 3
 
Christian Urban <christian.urban@kcl.ac.uk> 
parents: 
441 
diff
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: 
441 
diff
changeset
 | 
41  | 
|
| 
 
eda26fa6d3ec
updated to scala 3
 
Christian Urban <christian.urban@kcl.ac.uk> 
parents: 
441 
diff
changeset
 | 
42  | 
// implicit conversion from Doubles to Complex  | 
| 
 
eda26fa6d3ec
updated to scala 3
 
Christian Urban <christian.urban@kcl.ac.uk> 
parents: 
441 
diff
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: 
441 
diff
changeset
 | 
117  | 
  for (y <- (0 until H)) {
 | 
| 
 
eda26fa6d3ec
updated to scala 3
 
Christian Urban <christian.urban@kcl.ac.uk> 
parents: 
441 
diff
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: 
441 
diff
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: 
441 
diff
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))  |