01. Serial Version: Solving Heat Conduction on the CPU
You write the program heat1d_serial, which solves the problem in the common setup on a single-process CPU. The equations and how to check them are explained in Chapter 10.
What you use
Item |
Header |
|---|---|
|
|
|
|
|
|
|
|
Include it with the miso/ prefix, as in #include <miso/array1d.hpp>.
What you build
Arrays
Add the ghost cells at both ends to the number of physical cells \(N\), and allocate
Array1D<Real> u(N + 2)and the destination of the update,u_new(N + 2).Physical cell \(j\) (\(j = 0, \dots, N-1\)) is stored at array index
j + 1. The range of the physical cells isRange1D{1, N + 1}.
Steps
Initial condition: With
for_each, put \(\sin(\pi x_j)\) into the physical cells.Time integration: Repeat the following \(n_\mathrm{steps}\) times.
Put the boundary-condition values into the ghost cells:
u[0] = -u[1],u[N+1] = -u[N]. So that you can move this to the GPU later, write it insidefor_each(backend::Host{}, Range1D{0, 1}, ...).With
for_each, compute the following expression for the physical cells. Readuand write tou_new.u_new[i] = u[i] + r * (u[i-1] - 2 u[i] + u[i+1])
Write
2asReal(2). Here \(r = \kappa\Delta t/\Delta x^2\).Swap the arrays with
std::swap(u, u_new);.
Error: With
reduce, compute the maximum difference from the discrete solution and the maximum difference from the analytical solution.Output: Print the 3 lines described in the common setup, and write the result to
result_N<N>_serial.txt(result_N64_serial.txtfor \(N = 64\)).
Points to watch
Inside lambdas, use a view (
auto uv = u.view();), not theArray1D.After
std::swap, the view points to the data from before the swap. Obtain the view again every time inside the step loop.Write the maximum operation of
reduceasMISO_LAMBDA(Real a, Real b) { return a > b ? a : b; }. The initial value isReal(0)(the absolute value of a difference is at least 0, soReal(0)serves in place of the identity element of the maximum).Compute the coefficient of the discrete solution, \(G^{n_\mathrm{steps}}\), only once on the host as
std::pow(G, n_steps)(<cmath>), and capture it in the lambda.
What to check
Initial condition: Run the time integration for 0 steps (or write the output right after the initial condition), and check that \(u_j = \sin(\pi x_j)\).
One step: Advance just one step instead of \(n_\mathrm{steps}\), and check that the maximum difference from the discrete solution \(G\sin(\pi x_j)\) is about \(10^{-15}\).
Default input (\(N = 64\)): Check that you get the following values.
N = 64, steps = 1024, r = 0.4000 max error (discrete) = 1.249e-14 max error (exact) = 1.034113e-04
max error (discrete)is a rounding error, so it changes with the compiler and the way you write the code. It is fine as long as it is well below \(10^{-12}\).max error (exact)should agree to the 4th or 5th digit.Order of convergence: Run with \(N = 32, 64, 128, 256, 512\), tabulate
max error (exact), and compute the ratios of neighboring values and the order of convergence \(p = \log_2(E_N/E_{2N})\).\(N\)
steps
max error (exact)32
256
4.136835e-04
64
1024
1.034113e-04
128
4096
2.585224e-05
256
16384
6.463023e-06
512
65536
1.615753e-06
Every \(p\) is close to 2.000.
max error (discrete)is at most \(10^{-11}\) for every \(N\).Stability condition (optional): Run
./heat1d_serial 64 0.6and check that the error becomes very large (the computation breaks down). Also check that./heat1d_serial 64 0.5does not break down.
If the difference from the discrete solution is large, check in order the ghost-cell values, the range of the physical cells (the upper limit of Range1D is excluded), and the expression for \(x_j\) (the \(+1/2\) and the index offset). If it agrees with the discrete solution but the difference from the analytical solution differs from the table, check \(\Delta t\), \(n_\mathrm{steps}\), and \(t_\mathrm{end}\).
Criteria for moving on
Once you obtain the values in 3 and 4 above, move on to the 02. MPI version. Keep the files you wrote, such as result_N64_serial.txt, because you will use them in the comparison with the MPI version.