## The aim

Imagine we have a square coaster upon which we place a hot mug of tea. We wish to model the distribution of temperature across the coaster over time. For the sake of simplicity we will model the coaster only in two dimensions and we take the initial temperature across the surface to be except for a circle where the rim of the bottom of the mug touches the coaster, at which the temperature is .

In this post I’m going to show how we can model the heat equation succinctly in F#. I’m going to consider the two-dimensional case and approximate the solution at discrete spatial mesh points and at discrete time periods.

We will also plot the results by mapping the temperature onto the brightness (i.e. a heat or intensity map).

## The mathematics

In two dimensions the heat equation – taking the size of the coaster to be 100mm square – is given by:

where represents the temperature at time and at coordinates .

We need to apply boundary conditions at the edges of the coaster. We will assume for simpliciy that the temperature along the edges of the coaster remains constant, that is:

We also need to set our initial conditions:

To model this in F# we are going to represent the surface of the coaster using a 100×100 matrix (the matrix class is included in the F# powerpack).

Using the Euler method we can convert our continuous differential equation into a discrete difference equation:

For some constant which represents the thermal conductivity of the surface. Note that here is a natural number representing discrete time values.

## Show me the code!

The F# code runs very close to the mathematics so it should be self-documenting (although I’ve added some comments for readability). Plotting the results is relatively straightforward: we normalize the temperatures and represent them as shades of grey, white being hottest and black being coolest.

#light open Microsoft.FSharp.Math open System.Drawing open System.Drawing.Imaging open System.Windows.Forms open Microsoft.FSharp.Collections open System.Linq // Flattens a 2D array into a sequence let array2D_to_seq arr = seq {for i in 0..Array2D.length1 arr - 1 do for j in 0..Array2D.length2 arr - 1 do yield arr.[i,j]} // Find maximum value in a matrix let max_value_in_matrix m = m |> Matrix.toArray2D |> array2D_to_seq |> PSeq.max // Normalizes a matrix so its maximum value is 1 let normalize_matrix m = m * (1.0/(max_value_in_matrix m)) let mug_diameter = 50.0 //mm let coaster_length = 100.0 //mm let tolerance = 5.0 //we're operating on discrete space so the rim of the mug needs to have some thickness let num_steps = 1000 //number of iterations to be modelled // Number of rows and columns in the matrix let rows = (int)coaster_length let cols = (int)coaster_length // Equation for a circle let circle r x y = (x-coaster_length/2.0)**2.0 + (y-coaster_length/2.0)**2.0 - (mug_diameter/2.0)**2.0 // Inital conditions function let initialValues (x:int) (y:int) = match x,y with | (x,y) when circle (mug_diameter/2.0) (float(x)) (float(y)) >= 0.0 && circle (mug_diameter/2.0) (float(x)) (float(y)) <= tolerance**2.0 -> 80.0 |_ -> 20.0 // Create matrix representing initial conditions let initialConditions = Matrix.init rows cols initialValues |> normalize_matrix let c = 0.6 //Thermal conductivity let delta_t = ((1.0) / 2.0*c)/2.0 //Time interval // Our difference equation let rec temp_at x y (o:float) (l:float) (r:float) (t:float) (b:float) = o + c * delta_t * (r+l+4.0*o+t+b) // Mapping matrix u(t) to u(t+1) let newMatrix (m:matrix) = m |> Matrix.mapi(fun i j temp -> match (i,j) with | (i,j) when i = 0 || j = 0 || i = rows-1 || j = cols-1 -> 0.0 //Boundary conditions |_ -> temp_at i j (m.[i,j]) (m.[i-1,j]) (m.[i+1,j]) (m.[i,j+1]) (m.[i,j-1])) // Recursive function to determine the temperatures at time t let rec heatmap_at t = match t with | 0 -> initialConditions |_ -> heatmap_at (t-1) |> newMatrix let format = Imaging.PixelFormat.Format24bppRgb let toBitmap (arr:Color[,]) = // Create the bitmap let image = new Bitmap(arr.GetLength(0),arr.GetLength(1),Imaging.PixelFormat.Format24bppRgb) for i=0 to image.Width-1 do for j=0 to image.Height-1 do image.SetPixel(i, j, (arr.[i,j])) done done image let intensityMap intensity = Color.FromArgb((int (intensity * 255.0)),(int (intensity * 255.0)),(int (intensity * 255.0))) let intensities = heatmap_at num_steps |> normalize_matrix |> Matrix.toArray2D |> Array2D.map intensityMap let heatBitmap = intensities |> toBitmap let form = new Form( Text = "F# Heat Map", Size = heatBitmap.Size) let pic_box = new PictureBox( BorderStyle = BorderStyle.Fixed3D, Image = heatBitmap, Size = heatBitmap.Size, Dock = DockStyle.Fill, SizeMode = PictureBoxSizeMode.StretchImage) form.Controls.Add( pic_box ) #if INTERACTIVE form.Show() #else Application.Run(form) #endif

## Let’s take it for a spin!

Here I have taken snapshots at discrete times 0, 50, 100, …, 1000 with .

Quite an impressive simulation for just 100 lines of code – including comments and white space!

## References

Parallel Numerical Solution of 2-D Heat Equation, Verena Horak and Peter Gruber.

The Heat Equation, Wikipedia

Euler method, Wikipedia