This program solves the transient heat equation in deal.II using MPI. A parallel solver for the transient heat equation is highly valuable in numerous fields where efficient, large-scale simulations of heat transfer are required. In engineering, it models heat dissipation in electronics, engines, and industrial systems, while in climate science, it simulates heat flow in the atmosphere and oceans. Geophysical applications use it to study heat transfer in the Earth's crust, and coupled systems like conjugate heat transfer in fluids or thermoelasticity in structures rely on it for accurate, high-resolution solutions. Parallel solvers are essential for handling fine meshes, small time steps, and real-time applications, leveraging modern high-performance computing hardware to reduce computation time.
\begin{align*}
\frac{\partial u(\boldsymbol{x}, t)}{\partial t} - \Delta u(\boldsymbol{x}, t) &= f(\boldsymbol{x}, t),
&& \forall \boldsymbol{x} \in \Omega, \ t \in (0, T), \\
u(\boldsymbol{x}, 0) &= u_0(\boldsymbol{x}),
&& \forall \boldsymbol{x} \in \Omega, \\
u(\boldsymbol{x}, t) &= g(\boldsymbol{x}, t),
&& \forall \boldsymbol{x} \in \partial \Omega, \ t \in (0, T).
\end{align*}
Here, \(u\) is the temperature and \(t\) is the time.
An animation comparing results of temperature evolution from the current code with results of step-26 can be found here (left: serial code (step-26); right: current parallel implementation (80 procs)). We also perform a scaling study running the program for just one time step with around 50 M cells. The results can be found here
#include <deal.II/base/function.h>
#include <deal.II/base/quadrature_lib.h>
#include <deal.II/base/timer.h>
#include <deal.II/lac/generic_linear_algebra.h>
#define FORCE_USE_OF_TRILINOS
#
if defined(DEAL_II_WITH_PETSC) && !defined(DEAL_II_PETSC_WITH_COMPLEX) && \
!(defined(DEAL_II_WITH_TRILINOS) && defined(FORCE_USE_OF_TRILINOS))
using namespace dealii::LinearAlgebraPETSc;
#elif defined(DEAL_II_WITH_TRILINOS)
using namespace dealii::LinearAlgebraTrilinos;
#error DEAL_II_WITH_PETSC or DEAL_II_WITH_TRILINOS required
#include <deal.II/base/conditional_ostream.h>
#include <deal.II/base/index_set.h>
#include <deal.II/base/utilities.h>
#include <deal.II/distributed/grid_refinement.h>
#include <deal.II/distributed/tria.h>
#include <deal.II/dofs/dof_handler.h>
#include <deal.II/dofs/dof_tools.h>
#include <deal.II/fe/fe_q.h>
#include <deal.II/fe/fe_values.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/lac/affine_constraints.h>
#include <deal.II/lac/dynamic_sparsity_pattern.h>
#include <deal.II/lac/full_matrix.h>
#include <deal.II/lac/solver_cg.h>
#include <deal.II/lac/sparsity_tools.h>
#include <deal.II/lac/vector.h>
#include <deal.II/numerics/data_out.h>
#include <deal.II/numerics/error_estimator.h>
#include <deal.II/numerics/matrix_creator.h>
#include <deal.II/numerics/matrix_tools.h>
#include <deal.II/numerics/solution_transfer.h>
#include <deal.II/numerics/vector_tools.h>
namespace MPIHeatEquation {
template <
int dim>
class HeatEquation {
void assemble_system(
const double &time);
void output_results()
const;
void refine_mesh(
const unsigned int min_grid_level,
const unsigned int max_grid_level);
LA::MPI::SparseMatrix system_matrix;
LA::MPI::Vector locally_relevant_solution;
LA::MPI::Vector old_locally_relevant_solution;
LA::MPI::Vector system_rhs;
unsigned int timestep_number;
template <
int dim>
class RightHandSide :
public Function<dim> {
RightHandSide() :
Function<dim>(), period(0.2) {}
const unsigned int component = 0)
const override;
double RightHandSide<dim>::value(
const Point<dim> &p,
const unsigned int component)
const {
Assert(dim == 2, ExcNotImplemented());
const double point_within_period =
(time / period - std::floor(time / period));
if ((point_within_period >= 0.0) && (point_within_period <= 0.2)) {
if ((p[0] > 0.5) && (p[1] > -0.5))
}
else if ((point_within_period >= 0.5) && (point_within_period <= 0.7)) {
if ((p[0] > -0.5) && (p[1] > 0.5))
template <
int dim>
class BoundaryValues :
public Function<dim> {
const unsigned int component = 0)
const override;
double BoundaryValues<dim>::value(
const Point<dim> & ,
const unsigned int component)
const {
Assert(component == 0, ExcIndexRange(component, 0, 1));
HeatEquation<dim>::HeatEquation()
: mpi_communicator(MPI_COMM_WORLD),
computing_timer(mpi_communicator, pcout,
TimerOutput::never,
triangulation(mpi_communicator), fe(1), dof_handler(triangulation),
time_step(1. / 500), theta(0.5) {}
template <
int dim>
void HeatEquation<dim>::setup_system() {
dof_handler.distribute_dofs(fe);
<<
"===========================================" << std::endl
<<
" Number of active cells: "
<< triangulation.n_global_active_cells() << std::endl
<<
" Number of degrees of freedom: " << dof_handler.n_dofs()
locally_owned_dofs = dof_handler.locally_owned_dofs();
locally_relevant_solution.reinit(locally_owned_dofs, locally_relevant_dofs,
old_locally_relevant_solution.reinit(locally_owned_dofs,
locally_relevant_dofs, mpi_communicator);
system_rhs.reinit(locally_owned_dofs, mpi_communicator);
constraints.reinit(locally_owned_dofs, locally_relevant_dofs);
dsp,locally_owned_dofs, mpi_communicator,
system_matrix.reinit(locally_owned_dofs, locally_owned_dofs, dsp,
template <
int dim>
void HeatEquation<dim>::assemble_system(
const double &time) {
const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
const unsigned int n_q_points = quadrature_formula.size();
std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
std::vector<double> rhs_values(n_q_points);
std::vector<double> rhs_values_old(n_q_points);
for (
const auto &cell : dof_handler.active_cell_iterators())
if (cell->is_locally_owned())
for (
const unsigned int q_point : fe_values.quadrature_point_indices()) {
for (
const unsigned int i : fe_values.dof_indices()) {
for (
const unsigned int j : fe_values.dof_indices()) {
cell_mass_matrix(i, j) += fe_values.shape_value(i, q_point) *
fe_values.shape_value(j, q_point) *
cell_laplace_matrix(i, j) += fe_values.shape_grad(i, q_point) *
fe_values.shape_grad(j, q_point) *
((fe_values.shape_value(i, q_point) *
fe_values.shape_value(j, q_point)) +
(theta * time_step * fe_values.shape_grad(i, q_point) *
fe_values.shape_grad(j, q_point))) *
* * * struct InterferenceTaperTransform *
#define Assert(cond, exc)
#define AssertIndexRange(index, range)
void make_hanging_node_constraints(const DoFHandler< dim, spacedim > &dof_handler, AffineConstraints< number > &constraints)
void make_sparsity_pattern(const DoFHandler< dim, spacedim > &dof_handler, SparsityPatternBase &sparsity_pattern, const AffineConstraints< number > &constraints={}, const bool keep_constrained_dofs=true, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id)
@ update_values
Shape function values.
@ update_JxW_values
Transformed quadrature weights.
@ update_gradients
Shape function gradients.
@ update_quadrature_points
Transformed quadrature points.
void cell_matrix(FullMatrix< double > &M, const FEValuesBase< dim > &fe, const FEValuesBase< dim > &fetest, const ArrayView< const std::vector< double > > &velocity, const double factor=1.)
* * * ScaleZFunction< dim, Number, components >::ScaleZFunction * component(component)
* * if(update_pressure &update_flags) * compute_pressure(constitutive_request
rhs_function.set_time(time - time_step);
rhs_function.value_list(fe_values.get_quadrature_points(),
for (
const unsigned int q_point : fe_values.quadrature_point_indices())
for (const unsigned
int i : fe_values.dof_indices())
cell_rhs(i) += time_step * (1 - theta) *
(fe_values.shape_value(i, q_point) *
rhs_values_old[q_point] * fe_values.JxW(q_point));
constraints.distribute_local_to_global(
cell_matrix, cell_rhs, local_dof_indices, system_matrix, system_rhs);
template <
int dim>
void HeatEquation<dim>::solve_time_step() {
LA::MPI::Vector completely_distributed_solution(locally_owned_dofs,
LA::MPI::PreconditionAMG::AdditionalData
data;
data.symmetric_operator =
true;
LA::MPI::PreconditionAMG preconditioner;
preconditioner.initialize(system_matrix,
data);
solver.solve(system_matrix, completely_distributed_solution, system_rhs,
pcout <<
" Solved in " << solver_control.last_step() <<
" iterations."
constraints.distribute(completely_distributed_solution);
locally_relevant_solution = completely_distributed_solution;
template <
int dim>
void HeatEquation<dim>::output_results()
const {
data_out.add_data_vector(locally_relevant_solution,
"T");
for (
unsigned int i = 0; i < subdomain.size(); ++i)
subdomain(i) = triangulation.locally_owned_subdomain();
data_out.add_data_vector(subdomain,
"subdomain");
data_out.build_patches();
data_out.write_vtu_with_pvtu_record(
"./",
"solution", timestep_number,
void HeatEquation<dim>::refine_mesh(
const unsigned int min_grid_level,
const unsigned int max_grid_level)
{
triangulation.n_locally_owned_active_cells());
locally_relevant_solution, estimated_error_per_cell);
triangulation, estimated_error_per_cell, 0.6, 0.4);
if (triangulation.n_levels() > max_grid_level) {
triangulation.active_cell_iterators_on_level(max_grid_level))
if (cell->is_locally_owned())
cell->clear_refine_flag();
triangulation.active_cell_iterators_on_level(min_grid_level)) {
if (cell->is_locally_owned())
cell->clear_coarsen_flag();
LA::MPI::Vector previous_locally_relevant_solution(
locally_owned_dofs, locally_relevant_dofs, mpi_communicator);
previous_locally_relevant_solution = locally_relevant_solution;
triangulation.prepare_coarsening_and_refinement();
solution_trans.prepare_for_coarsening_and_refinement(
previous_locally_relevant_solution);
triangulation.execute_coarsening_and_refinement();
LA::MPI::Vector completely_distributed_solution(locally_owned_dofs,
solution_trans.interpolate(completely_distributed_solution);
constraints.distribute(completely_distributed_solution);
locally_relevant_solution = completely_distributed_solution;
template <
int dim>
void HeatEquation<dim>::run() {
<< " MPI rank(s)..." << std::endl;
const unsigned int initial_global_refinement = 2;
const unsigned int n_adaptive_pre_refinement_steps = 4;
triangulation.refine_global(initial_global_refinement);
unsigned int pre_refinement_step = 0;
LA::MPI::Vector completely_distributed_solution(locally_owned_dofs,
completely_distributed_solution);
old_locally_relevant_solution = completely_distributed_solution;
locally_relevant_solution = completely_distributed_solution;
const double end_time = 0.5;
while (time < end_time) {
void attach_dof_handler(const DoFHandler< dim, spacedim > &)
static void estimate(const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof, const Quadrature< dim - 1 > &quadrature, const std::map< types::boundary_id, const Function< spacedim, Number > * > &neumann_bc, const ReadVector< Number > &solution, Vector< float > &error, const ComponentMask &component_mask={}, const Function< spacedim > *coefficients=nullptr, const unsigned int n_threads=numbers::invalid_unsigned_int, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id, const types::material_id material_id=numbers::invalid_material_id, const Strategy strategy=cell_diameter_over_24)
std::vector< index_type > data
void hyper_L(Triangulation< dim > &tria, const double left=-1., const double right=1., const bool colorize=false)
unsigned int n_mpi_processes(const MPI_Comm mpi_communicator)
void refine_and_coarsen_fixed_fraction(::Triangulation< dim, spacedim > &tria, const ::Vector< Number > &criteria, const double top_fraction_of_error, const double bottom_fraction_of_error, const VectorTools::NormType norm_type=VectorTools::L1_norm)