In [1]:
import numpy as np
import matplotlib.pyplot as plt
Finite differences for heat equation¶
Simple 1D diffusion-only constant-conductivity heat equation is
The second-order spatial derivative of temperature can be approximated with finite differences:
Since \(x_1 - \Delta x = x_0\) and \(x_1 + \Delta x = x_2\), we get
where the last formula uses the short hand notation for indices. Note that here the superscripts do not mean “to power” but are indices for time steps.
Using the same short hand notation, we can approximate the time derivative of the temperature:
These approximations can be generalized:
And thus the full finite differences approximation of the heat equation can be written as
This applies to any \(q\) and \(i\). If we know \(T\) everywhere at time \(t_q\), we can calculate \(T\) at time \(t_{q+1} = t_q + \Delta t\).
Do¶
- How would the implicit (backward difference in time) approximation look like for the heat equation? Recall that in the implicit approximation we used the values from the next time step to approximate the derivative. What is the problem in calculating \(T_i^q\) using the implicit approximation?
Boundary conditions, initial conditions¶
To start calculating the values of \(T\), we need the initial values at some time \(t_0\). This temperature field is the so called initial condition for our problem. It is a type of boundary condition that applies to a temporally variable field.
(Spatial) boundary conditions are needed because when applying finite differences to a differential equation (in space), we need the surrounding values, such as \(T_{i+1}\) and \(T_{i-1}\) in the case of heat equation, to calculate the next value of \(T_{i}\). These values are not available if we are near the geometrical boundary of the problem.
FD, heat equation, and cooling of an intrusion¶
Next we will use finite differences to model the cooling of a (very large) magma intrusion within the crust. The crust is limited by the upper surface and the crust-mantle boundary at the bottom. It is 35 km thick and intruded by a horizontal, granitic magma intrusion (sill) that extends from 5 km to 10 km depth. Our task is to calculate the temperature distribution in the crust after 100 kyrs.
Our strategy for solving the problem in Python is following:
- Draw a picture!
- Decide on model geometry and physical parameters
- one-dimensional
- homogeneous physical parameters
- \(\Rightarrow\) we can use the equation above
- Set physical parameters (\(\rho\), \(C_p\), \(k\))
- Set geometrical parameters (\(L\), \(\Delta x\)
\(\Rightarrow\)
nx
) - Set total run time and time step size (\(t_{total}\),
\(\Delta t\) \(\Rightarrow\)
nt
) - Decide and set boundary condition values \(T_0\) and \(T_{nx-1}\)
- Create two arrays that hold the temperature, size
nx
elementsTnew
for \(T^{q+1}\)Tprev
for \(T^{q}\)
- Create an array for grid point locations (\(x\) coordinates),
size
nx
- Set
Tprev
to hold the initial temperature field- Linear geotherm throughout the crust, constant temperature within the sill
- Start the loop that iterates over all the time steps. Within the time
step:
- Set the temperature at the boundaries, according to boundary
conditions, at
Tnew[0]
andTnew[nx-1]
- Calculate remaining (internal grid points) within
Tnew
based on values ofTprev
- Prepare for next time step: Copy all values of
Tnew
toTprev
- Set the temperature at the boundaries, according to boundary
conditions, at
Do¶
- Use the following frame to start implementing the code. Once the code
seems to work, experiment with it:
- How does changing values of
Cp
andrho
affect the results? - Give
dt
values that are close to the critical time step. How do the results change? - Can the code handle intrusions that are only 500 m in width?
- How does changing values of
- What have silently assumed in the implementation of the code? E.g. can we model the problem in simplified 1D world? What kind of physical processes, that are likely to be present, have we omitted?
In [ ]:
# Frame for FD solution for heat diffusion
# Fill in lines marked with "(...)"
import numpy as np
import matplotlib.pyplot as plt
# Set material properties
rho = (...) # rock density, kg m^-3
Cp = (...) # rock heat capacity, J kg^-1 K^-1
k = (...) # rock heat conductivity, W m^-1 K^-1
# Set geometry and dimensions
L = (...) # thickness of the crust, m
nx = (...) # number of grid points, -
dx = L / (nx-1)
print("dx is", dx, "m")
silltop = (...) # depth of the top of the sill, m
sillbott = (...) # depth of the bottom of the sill, m
# Set total time and time step
t_total = (...) # total run time, s
dt = (...) # time step, s
nt = int(np.floor(t_total / dt))
print("Calculating", nt, "time steps")
# Set boundary temperature values and intrusion temperature
Tsurf = (...) # surface temperature, deg C
Tmoho = (...) # Moho temperature, deg C
Tintrusion = (...) # Intrusion temperature, deg C
# TODO: Create arrays to hold temperature fields.
# These need to have `nx` elements and can be
# set to zero for now.
Tnew = (...)
Tprev = (...)
# TODO: Create coordinates of the grid points.
# We want `x` to have evenly spaced values
# from zero to `L`, with `nx` elements.
# Use `np.linspace()`.
x = (...)
# Generate initial temperature field.
#
for ix in range(nx):
# TODO: Add condition that tests whether
# element `ix` of `x` is inside the sill.
if (...):
Tprev[ix] = Tintrusion
else:
# x[ix] is outside the sill.
# This assumes linear geotherm between
# surface temperature and moho temperature
Tprev[ix] = x[ix] * (Tmoho - Tsurf) / L
# Plot initial temperature field
plt.plot(Tprev, -x, '.-', label='T initial')
plt.legend()
plt.show()
# Start the loop over time steps
curtime = 0
while curtime < t_total:
curtime = curtime + dt
# TODO: Set boundary conditions.
# Set elements zero and `nx-1` of the new temperature field
# to surface temperature and Moho temperature, respectively
(...)
(...)
# Calculate next values of the internal grid points
for ix in range(1, nx-1):
# TODO: Calculate the new temperature at Tnew[ix] based
# on values in `Tprev`, `k`, `dt`, `rho` and `Cp`.
# Use FD approximation from above and rearrange it
# to get an expression for T_i^{q+1}. Be extra careful
# with correct placement of parentheses!
Tnew[ix] = (...)
# Copy values from Tnew to Tprev
Tprev[:] = Tnew[:]
# Plot the final temperature field
plt.plot(Tnew, -x, '.-', label='T final')
plt.legend()
plt.show()