Contents
Advection-Diffusion
Introduction
This tutorial provides a step-by-step guide for implementing a Advection-Diffusion problem using the Finite Element Method (FEM) in the deal.II library. We will focus on setting up the weak form of the equations, assembling the finite element system, applying appropriate boundary conditions, and solving the resulting linear system. This tutorial assumes familiarity with basic FEM concepts and provides a practical foundation for more advanced developments within the deal.II framework.
Theory
TBA
Implementation
Code Implementation Brekdown TBA
Results
Results for various Advection-Diffusion constants TBA
Complete code
#include <deal.II/base/quadrature_lib.h> #include <deal.II/base/function.h> #include <deal.II/base/timer.h> #include <deal.II/lac/generic_linear_algebra.h> #include <deal.II/lac/vector.h> #include <deal.II/lac/full_matrix.h> #include <deal.II/lac/precondition.h> #include <deal.II/lac/petsc_precondition.h> #include <deal.II/lac/affine_constraints.h> #include <deal.II/lac/dynamic_sparsity_pattern.h> #include <deal.II/lac/solver_gmres.h> #include <deal.II/grid/grid_generator.h> #include <deal.II/dofs/dof_handler.h> #include <deal.II/dofs/dof_tools.h> #include <deal.II/dofs/dof_accessor.h> #include <deal.II/grid/tria_accessor.h> #include <deal.II/fe/fe_values.h> #include <deal.II/fe/fe_system.h> #include <deal.II/fe/fe_q.h> #include <deal.II/grid/grid_refinement.h> #include <deal.II/numerics/vector_tools.h> #include <deal.II/numerics/matrix_tools.h> #include <deal.II/numerics/data_out.h> #include <deal.II/numerics/error_estimator.h> #include <deal.II/base/utilities.h> #include <deal.II/base/conditional_ostream.h> #include <deal.II/base/index_set.h> #include <deal.II/lac/sparsity_tools.h> #include <deal.II/distributed/tria.h> #include <deal.II/distributed/grid_refinement.h> #include <deal.II/distributed/solution_transfer.h> #include <deal.II/base/quadrature_point_data.h> #include <deal.II/base/tensor_function.h> #include <fstream> #include <iostream> #include <random> namespace diff { using namespace dealii; template <int dim> class Advection { public: Advection(); void run(); private: // Mesh generation and refinement void create_mesh(); void refine_mesh(); // Advection problem functions void set_initial_data(); void setup_constraints (); // Posso creare funz che applica BC o mettere a fine assembly matrix void setup(); void assemble(unsigned int current_timestep); void solve_linear_problem(); // Save the results - Disp, stress, reaction, energies void output_results(const unsigned int time) const; // Parallel setup and print on screen MPI_Comm mpi_communicator; const unsigned int n_mpi_processes; const unsigned int this_mpi_process; parallel::distributed::Triangulation<dim> triangulation; // Objects for Advection const FESystem<dim> fe; // FE System DoFHandler<dim> dof_handler; // DoF Handler IndexSet locally_owned_dofs; // IndexSet - Locally Owned IndexSet locally_relevant_dofs; // IndexSet - Locally relevant AffineConstraints<double> constraints; // Affine Constraint PETScWrappers::MPI::SparseMatrix system_matrix; // Matrix PETScWrappers::MPI::Vector system_rhs; // RHS PETScWrappers::MPI::Vector local_solution_C1; // MPI Split solution PETScWrappers::MPI::Vector local_solution_old_C1; // MPI Split solution PETScWrappers::MPI::Vector distributed_solution_C1; // Full Solution PETScWrappers::MPI::Vector distributed_old_C1; // This is for the refinement const QGauss<dim> quadrature_formula; // Quadrature formula // Domain dimensions double L = 1; double H = 0.1; double W = 0.1; const unsigned int nx = 30; const unsigned int ny = 3; const unsigned int nz = 3; // Advection coefficient [m/s] double velocity = 1; // Advection coefficients [m^2/s] double D = 1e-2; // Initial concentrations [mol/m^3] double C1_0 = 0; // Time stepping [s] double dt = 0.01; double T_tot = 1; const unsigned int n_steps = static_cast<unsigned int>(std::round(T_tot / dt)); ConditionalOStream pcout; TimerOutput computing_timer; }; // End of diff class // Constructor template <int dim> Advection<dim>::Advection() : mpi_communicator(MPI_COMM_WORLD) , n_mpi_processes(Utilities::MPI::n_mpi_processes(mpi_communicator)) , this_mpi_process(Utilities::MPI::this_mpi_process(mpi_communicator)) , triangulation (mpi_communicator) , fe (FE_Q<dim> (1), 1) , dof_handler (triangulation) , quadrature_formula (fe.degree + 1) , pcout (std::cout, this_mpi_process == 0) , computing_timer (mpi_communicator, pcout, TimerOutput::never, TimerOutput::wall_times) {} // ----------------------------- // ----------- MESH ------------ // ----------------------------- template <int dim> void Advection<dim>::create_mesh() { // 1) Definisco la suddivisione in base a dim std::vector<unsigned int> repetitions; if constexpr (dim == 2) repetitions = {nx, ny}; else // dim == 3 repetitions = {nx, ny, nz}; // 2) Crea la mesh Point<dim> p1, p2; if constexpr (dim == 2) { p1 = Point<dim>(0, 0); p2 = Point<dim>(L, H); } else { p1 = Point<dim>(0, 0, 0); p2 = Point<dim>(L, H, W); } GridGenerator::subdivided_hyper_rectangle(triangulation, repetitions, p1, p2); // Set Fce IDs for (const auto &cell : triangulation.active_cell_iterators ()) { for (const auto &face : cell->face_iterators ()) { if (face->at_boundary ()) { const auto center = face->center (); if (std::fabs (center(0) - 0) < 1e-12) face->set_boundary_id (0); else if (std::fabs (center(0) - L) < 1e-12) face->set_boundary_id (1); else if (std::fabs (center(1) - 0) < 1e-12) face->set_boundary_id (2); else if (std::fabs (center(1) - H) < 1e-12) face->set_boundary_id (3); // Controlla se siamo in 3D prima di accedere a center(2) if constexpr (dim == 3) { if (std::fabs(center(2) - 0) < 1e-12) face->set_boundary_id(4); else if (std::fabs(center(2) - W) < 1e-12) face->set_boundary_id(5); } } } } // Initialize dof handler and extact Owned/Relevant DoFs dof_handler.distribute_dofs (fe); locally_owned_dofs = dof_handler.locally_owned_dofs (); locally_relevant_dofs = DoFTools::extract_locally_relevant_dofs (dof_handler); // Initialize the old solution for updates purposes local_solution_old_C1.reinit (locally_owned_dofs, locally_relevant_dofs, mpi_communicator); // Initialize the old solution for refinement purposes distributed_old_C1.reinit (locally_owned_dofs, mpi_communicator); pcout << "No. of levels in triangulation: " << triangulation.n_global_levels () << std::endl; pcout << " Number of locally owned cells on the process: " << triangulation.n_locally_owned_active_cells () << std::endl; pcout << "Number of global cells:" << triangulation.n_global_active_cells () << std::endl; pcout << " Total Number of globally active cells: " << triangulation.n_global_active_cells () << std::endl << " Number of degrees of freedom for Advection: " << dof_handler.n_dofs () << std::endl; } template <int dim> void Advection<dim>::refine_mesh() { // 1) Stima dell’errore su C2 Vector<float> estimated_error_per_cell(triangulation.n_locally_owned_active_cells()); KellyErrorEstimator<dim>::estimate( dof_handler, QGauss<dim-1>(fe.degree + 1), std::map<types::boundary_id, const Function<dim>*>(), local_solution_C1, estimated_error_per_cell); // 2) Prepare il solution transfer (ghosted!) parallel::distributed::SolutionTransfer<dim, PETScWrappers::MPI::Vector> soltransC1(dof_handler); // 3) Refinement/coarsening parallel::distributed::GridRefinement::refine_and_coarsen_fixed_fraction( triangulation, estimated_error_per_cell, 0.25, 0.1); if (triangulation.n_global_levels() >= 4) { for (const auto &cell : triangulation.active_cell_iterators_on_level(3)) if (cell->is_locally_owned()) cell->clear_refine_flag(); } // 4) Esegui coarsening/refinement triangulation.prepare_coarsening_and_refinement(); soltransC1.prepare_for_coarsening_and_refinement (local_solution_C1); triangulation.execute_coarsening_and_refinement(); // 5) Redistribuisci DoF dof_handler.distribute_dofs(fe); locally_owned_dofs = dof_handler.locally_owned_dofs(); locally_relevant_dofs = DoFTools::extract_locally_relevant_dofs(dof_handler); // 6) Reinit dei nuovi vettori distrib distributed_old_C1.reinit(locally_owned_dofs, mpi_communicator); soltransC1.interpolate (distributed_old_C1); // 8) Riapplico vincoli setup(); constraints.distribute(distributed_old_C1); // 9) Aggiorna anche i ghost local_solution_old_C1.reinit(locally_owned_dofs, locally_relevant_dofs, mpi_communicator); local_solution_C1 = distributed_old_C1; // Per output local_solution_old_C1 = distributed_old_C1; // Per estrarre valore sol al passo next } // ----------------------------- // -------- Advection ---------- // ----------------------------- template <int dim> class BoundaryConcentration : public Function<dim> { public: BoundaryConcentration() : Function<dim>(), time(0.0) {} // Metodo per impostare il valore del tempo void set_time(double t) { time = t; } virtual double value(const Point<dim> &p, const unsigned int component = 0) const override { // Parametri per il centro della palla const double y0 = 0.05; const double z0 = 0.05; const double radius = 0.1 / 4.0; // Raggio della palla // Calcolo della distanza al quadrato dal centro (punto di riferimento) double distance_squared = 0.0; if constexpr (dim == 2) { // 2D: distanza tra il punto p e il centro (y0, z0) distance_squared = (p[1] - y0) * (p[1] - y0); } else if constexpr (dim == 3) { // 3D: distanza tra il punto p e il centro (y0, z0) distance_squared = (p[1] - y0) * (p[1] - y0) + (p[2] - z0) * (p[2] - z0); } else { AssertThrow(false, ExcNotImplemented()); return 0.0; } // Condizione per determinare se il punto è all'interno della sfera if (distance_squared <= radius * radius) { // Modifica dell'ampiezza in base al tempo (ora il tempo è passato esplicitamente) double amplitude = 0.0; if (time < 5.0) { amplitude = time / 5.0; // Cresce linearmente da 0 a 1 } else if (time < 10.0) { amplitude = 1.0 - (time - 5.0) / 5.0; // Decresce da 1 a 0 } // Calcolo della "macchia" gaussiana const double width = 0.01; // larghezza gaussiana if constexpr (dim == 2) { // 2D: (x, y) => macchia su y return amplitude * std::exp(-((p[1] - y0) * (p[1] - y0)) / (width)); } else if constexpr (dim == 3) { // 3D: (x, y, z) => macchia su y,z const double r2 = (p[1] - y0) * (p[1] - y0) + (p[2] - z0) * (p[2] - z0); return amplitude * std::exp(-r2 / width); } else { AssertThrow(false, ExcNotImplemented()); return 0.0; } } else { // Se il punto non è dentro la sfera, ritorna 0 return 0.0; } } private: double time; // Variabile per il tempo }; template<int dim> void Advection<dim>::setup_constraints () { constraints.clear(); constraints.reinit(locally_relevant_dofs); DoFTools::make_hanging_node_constraints(dof_handler, constraints); constraints.close(); } template<int dim> void Advection<dim>::setup() { TimerOutput::Scope ts(computing_timer, "setup_system"); // Initialize the dofs contained locally as well as those of the near cells locally_owned_dofs = dof_handler.locally_owned_dofs (); locally_relevant_dofs = DoFTools::extract_locally_relevant_dofs (dof_handler); local_solution_C1.reinit (locally_owned_dofs, locally_relevant_dofs, mpi_communicator); // Initialize the contraints objects for BC and Hanging Nodes during refinement system_rhs.reinit (locally_owned_dofs, mpi_communicator); distributed_solution_C1.reinit (locally_owned_dofs, mpi_communicator); setup_constraints (); // Qui unizializzo gli hanging nodes constrains (e le BC volendo) // Sparsity pattern initialization (where matrix has nonzero entries) DynamicSparsityPattern dsp(locally_relevant_dofs); DoFTools::make_sparsity_pattern (dof_handler, dsp, constraints, false); SparsityTools::distribute_sparsity_pattern (dsp, dof_handler.locally_owned_dofs (), mpi_communicator, locally_relevant_dofs); // Advection Matrix system_matrix.reinit (locally_owned_dofs, locally_owned_dofs, dsp, mpi_communicator); } template <int dim> void Advection<dim>::set_initial_data() { TimerOutput::Scope ts(computing_timer, "set_initial_data"); // 1) Inizializza i vettori globali con i valori iniziali costanti distributed_solution_C1 = C1_0; // 2) Sincronizza i ghost‑values sui vettori globali distributed_solution_C1.compress(VectorOperation::insert); // 3) Applica le condizioni di Dirichlet (especialmente su C2) // Questo usa setup_constraints e constraints già definiti setup_constraints(); constraints.distribute(distributed_solution_C1); // 4) Copia nei vettori locali “split” e in quelli “old” local_solution_C1 = distributed_solution_C1; local_solution_old_C1 = distributed_solution_C1; // 5) Aggiorna i ghost‑values sui vettori locali local_solution_C1.update_ghost_values(); local_solution_old_C1.update_ghost_values(); } template<int dim> void Advection<dim>::assemble(unsigned int current_timestep) { TimerOutput::Scope ts (computing_timer, "assembly"); // Clean the current matrices system_rhs = 0; system_matrix = 0; // Initialize the FEValues Objects FEValues<dim> fe_values (fe, quadrature_formula, update_values | update_gradients | update_quadrature_points | update_JxW_values); // Number of DoFs and Gauss points per cell const unsigned int n_q_points = quadrature_formula.size (); const unsigned int dofs_per_cell = fe.n_dofs_per_cell (); // Local Matrices and Vector FullMatrix<double> cell_matrix (dofs_per_cell, dofs_per_cell); Vector<double> cell_rhs (dofs_per_cell); // Save cell values for C1 std::vector<double> sol_C1(n_q_points); // Create vector to store the local dof indices std::vector<types::global_dof_index> local_dof_indices (dofs_per_cell); // Create the diffusion tensor SymmetricTensor<2, dim> diffusion_tensor; for (unsigned int i=0; i<dim; i++) diffusion_tensor[i][i] = D; Tensor<1,dim> velocity_tensor; velocity_tensor[0] = velocity; // loop over the cells for (const auto &cell : dof_handler.active_cell_iterators ()) { if (cell->is_locally_owned ()) { // Reset the local matrix/rhs of the cell cell_matrix = 0; cell_rhs = 0; fe_values.reinit (cell); fe_values.get_function_values(local_solution_old_C1, sol_C1); // loop over the gauss point of the cell for (const unsigned int q_point : fe_values.quadrature_point_indices()) { const double JxW = fe_values.JxW(q_point); // Jacobiano * peso del punto di quadratura for (const unsigned int i : fe_values.dof_indices()) { const double phi_i = fe_values.shape_value(i, q_point); const Tensor<1, dim> grad_phi_i = fe_values.shape_grad(i, q_point); for (const unsigned int j : fe_values.dof_indices()) { const double phi_j = fe_values.shape_value(j, q_point); const Tensor<1, dim> grad_phi_j = fe_values.shape_grad(j, q_point); // Mass term (1/Δt) * (phi_i * phi_j) cell_matrix(i, j) += (phi_i * phi_j) / dt * JxW; // Convection term: phi_i * (v ⋅ ∇phi_j) cell_matrix(i, j) += phi_i * (velocity_tensor * grad_phi_j) * JxW; // Diffusion term: (∇phi_i) ⋅ D ⋅ (∇phi_j) cell_matrix(i, j) += grad_phi_i * diffusion_tensor * grad_phi_j * JxW; } // RHS term: (1/Δt) * phi_i * u^n cell_rhs(i) += phi_i * sol_C1[q_point] / dt * JxW; // Optional source term f(x): add here if needed // cell_rhs(i) += phi_i * source_value(q_point) * JxW; } } cell->get_dof_indices (local_dof_indices); constraints.distribute_local_to_global (cell_matrix, cell_rhs, local_dof_indices, system_matrix, system_rhs); } } system_matrix.compress (VectorOperation::add); system_rhs.compress (VectorOperation::add); // Now set the BC std::map<types::global_dof_index, double> boundary_values; BoundaryConcentration<dim> inflow_function; inflow_function.set_time(current_timestep); VectorTools::interpolate_boundary_values(dof_handler, 0, // boundary_id per x = 0 inflow_function, // la tua funzione gaussiana boundary_values); PETScWrappers::MPI::Vector tmp(locally_owned_dofs, mpi_communicator); MatrixTools::apply_boundary_values(boundary_values, system_matrix, tmp, system_rhs, false); distributed_solution_C1 = tmp; } template<int dim> void Advection<dim>::solve_linear_problem() { TimerOutput::Scope ts(computing_timer, "solve_linear_system"); SolverControl solver_control(1000, 1e-12 * system_rhs.l2_norm()); SolverGMRES<PETScWrappers::MPI::Vector> solver(solver_control); // Precondizionatore identità dealii::PreconditionIdentity preconditioner; solver.solve(system_matrix, distributed_solution_C1, system_rhs, preconditioner); pcout << " Solved in " << solver_control.last_step() << " iterations." << std::endl; constraints.distribute(distributed_solution_C1); } // ----------------------------- // ---------- OUTPUT ----------- // ----------------------------- // Solution template <int dim> void Advection<dim>::output_results(const unsigned int load_step) const { DataOut<dim> data_out; // Aggiungo i tre campi SCALARI: C1 data_out.add_data_vector(dof_handler, local_solution_C1, "C1"); // Subdomain (per mappare MPI rank) Vector<double> subdomain(triangulation.n_active_cells()); 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(); const std::string filename = data_out.write_vtu_with_pvtu_record("./", "solution", load_step, mpi_communicator); if (this_mpi_process == 0) { static std::vector<std::pair<double, std::string>> times_and_names; times_and_names.emplace_back(load_step, filename); std::ofstream pvd_output("./solution.pvd"); DataOutBase::write_pvd_record(pvd_output, times_and_names); } } // ----------------------------- // ----------- RUN ------------- // ----------------------------- template <int dim> void Advection<dim>::run() { Timer timer; timer.start (); pcout << "Running on " << Utilities::MPI::n_mpi_processes (mpi_communicator) << " MPI rank(s)..." << std::endl; create_mesh(); setup(); set_initial_data(); for (unsigned int step = 0; step <= n_steps; ++step) { const double time = step * dt; pcout << " --- Time step : " << time << std::endl; assemble(step); solve_linear_problem(); // --- C1: update & backup --- local_solution_C1 = distributed_solution_C1; local_solution_C1.update_ghost_values(); local_solution_old_C1 = distributed_solution_C1; local_solution_old_C1.update_ghost_values(); // ---------- Refinement & Output ---------- refine_mesh(); // dentro questa viene fatto setup() + interpolate pcout << " Total New Number of globally active cells: " << triangulation.n_global_active_cells () << std::endl << " New Number of degrees of freedom: " << dof_handler.n_dofs () << std::endl; output_results(step); computing_timer.print_summary(); computing_timer.reset(); } timer.stop (); pcout << "Total run time: " << timer.wall_time () << " seconds." << std::endl; } } // End of namespace Advection // ----------------------------- // ---------- MAIIN ------------ // ----------------------------- int main (int argc, char *argv[]) { try { using namespace dealii; using namespace diff; Utilities::MPI::MPI_InitFinalize mpi_initialization (argc, argv, 1); Advection<2> Advection; Advection.run (); } catch (std::exception &exc) { std::cerr << std::endl << std::endl << "----------------------------------------------------" << std::endl; std::cerr << "Exception on processing: " << std::endl << exc.what () << std::endl << "Aborting!" << std::endl << "----------------------------------------------------" << std::endl; return 1; } catch (...) { std::cerr << std::endl << std::endl << "----------------------------------------------------" << std::endl; std::cerr << "Unknown exception!" << std::endl << "Aborting!" << std::endl << "----------------------------------------------------" << std::endl; return 1; } return 0; }