Parallel finite-element solver for the 2D scalar wave equation
on a rectangular domain
| Method | Parameters | Key properties |
|---|---|---|
| Theta-method |
|
|
| Newmark-β |
|
nmpde-wave-equation/
├── include/ # C++ headers
│ ├── WaveEquationBase.hpp # abstract base (mesh, FE, logging, output)
│ ├── WaveNewmark.hpp # Newmark-beta solver
│ ├── WaveTheta.hpp # theta-method solver
│ └── ParameterReader.hpp # JSON/PRM parameter parser
├── src/ # C++ sources
│ ├── WaveEquationBase.cpp
│ ├── WaveNewmark.cpp
│ ├── WaveTheta.cpp
│ ├── ParameterReader.cpp
│ ├── main-newmark.cpp # executable entry point (Newmark)
│ └── main-theta.cpp # executable entry point (theta)
├── parameters/ # JSON parameter files for different test cases
├── scripts/ # Python sweep scripts + PBS job files
├── analysis/ # Jupyter notebooks for post-processing
├── results/ # simulation output (VTU, CSV) — git-ignored
├── build/ # CMake build directory — git-ignored
├── docs/ # Docsygen documentation
├── presentation/ # LaTeX presentation
└── report/ # LaTeX report
To get the automatically generated Doygen documentation:
cd docs/
./generate-docs.shThen open the file docs/html/index.html in you favorite browser.
- C++17 compiler with MPI support
- deal.II ≥ 9.3.1 (with Trilinos enabled)
You can get the Apptainer containter used in the project by executing the following commands on a terminal window
mkdir -p $HOME/apptainer-tmp/
mkdir -p $HOME/apptainer-cache/
export APPTAINER_TMPDIR=$HOME/apptainer-tmp/
export APPTAINER_CACHEDIR=$HOME/apptainer-cache/
apptainer pull docker://quay.io/pjbaioni/amsc_mk:2025and afterwards load the container and the needed modules
apptainer shell /path/to/amsc_mk_2025.sif
source /u/sw/etc/bash.bashrc
module load gcc-glibc dealiimkdir build
cd build
cmake ..
make -jThis produces two executables: main-theta and main-newmark.
Both executables take a single command-line argument i.e. the path to a JSON parameter file.
cd build
mpirun -np 4 ./main-theta ../parameters/standing-mode-wsol.json
mpirun -np 4 ./main-newmark ../parameters/gaussian-pulse.jsonIf no argument is given, the default ../parameters/sine-membrane.json is used.
A JSON parameter file with scalar entries and function subsections
Several already available parameter files are provided in parameters/ (e.g. Gaussian pulse, Ricker wavelet, ...).
The results of the simulation are written to the following folder /results/<problem_name>/run-R...-N...-dt.../:
solution_*.vtuorsolution_*.pvtu→ displacement, velocity, and eventually the exact solution, ideal for being imported in ParaViewenergy.csv→ discrete energy time serieserror.csv→ L2 and H1 error and comparison w.r.t the exact solution, if availableprobe.csv→ point probe at the domain centreiterations.csv→ Conjugate Gradient (CG) method iteration countsconvergence.csv→ execution summary appended from different runs
Abstract base class providing:
- Mesh creation (
subdivided_hyper_rectangle_with_simplices) and parallel partitioning - FE space setup (
FE_SimplexP,QGaussSimplex) - DoF distribution
- Energy computation
$E^n = \tfrac12 (\mathbf{v}^T M \mathbf{v} + \mathbf{u}^T K \mathbf{u})$ - L2 / H1 error integration against an exact solution
- VTU/PVTU output, CSV logging, divergence detection
Inherits from WaveEquationBase. Rewrites the wave equation as a first-order
system and applies the theta-method. Each time step solves two SPD systems
(one for
Inherits from WaveEquationBase. Uses the Newmark-$\beta$ family. Each time
step solves one SPD system for the acceleration
Thin wrapper around deal.II's ParameterHandler. Declares scalar parameters
and function subsections, parses JSON/PRM files, and initialises FunctionParser
objects (supporting symbolic pi constants).
All scripts live in scripts/ and drive parametric studies by repeatedly
invoking the C++ executables with different parameter combinations.
| Python script | Purpose |
|---|---|
convergence_sweep.py |
Sweep over (scheme, Nel, R, dt) to study spatial and temporal convergence, applying also Courant-Friedrichs-Lewy (CFL) filtering for explicit methods. |
dissipation_dispersion_sweep.py |
Fix the mesh, sweep over dt for each scheme, logging energy values, errors and point-probing at each step to analyse numerical dissipation and dispersion. |
scalability_sweep.py |
Fix discretisation, measure the wall-clock time for a given number of MPI processes. |
python3 convergence_sweep.py \
--nprocs 4 \
--nel 10 20 40 80 \
--r 1 2 \
--dt 0.01 0.005 0.001 \
--T 1.0 \
--schemes theta-0.5 newmark-0.25 \
--timeout 600 \
--cfl-safety 0.9Each script produces CSV result files that are later read by the analysis notebooks.
Three PBS job scripts are provided in scripts/:
| PBS script | What it runs |
|---|---|
convergence_all.pbs |
convergence_sweep.py with 16 MPI processes |
dissipation_dispersion_all.pbs |
dissipation_dispersion_sweep.py with 16 MPI processes |
scalability_all.pbs |
scalability_sweep.py for p = 1, 2, 4, 8, 16 (sequential) |
You should be sure to change the directories used in the scripts before submitting the job, and put your desired one.
cd wave-equation/scripts
qsub convergence_all.pbs- Copy the project to
scratch_local— thescripts/,parameters/andbuild/directories are copied to/scratch_local/nmpde-<name>_${PBS_JOBID}/. All computation happens on this fast node-local temporary storage and get much better I/O performance for the many small files produced by the sweeps. - Run the Python sweep script from the scratch directory with
--use-pbs-nodefileand--bind-to-corebinding MPI processes to cores, not to threads - Copy results back to a persistent location under the user's home directory (CSVs, compressed logs, raw convergence files).
Other notes:
OMP_NUM_THREADS=1is set to prevent OpenMP oversubscription.- For scalability tests, select a free node, so that memory bandwidth is not shared with other jobs.
- Core binding is enabled via
--bind-to core --map-by socket.
The analysis/ folder contains Jupyter notebooks that read the .csv files produced by the Python scripts.
| Notebook | Content |
|---|---|
convergence-analysis.ipynb |
Convergence plots (relative L2/H1 error vs. |
dissipation-dispersion-analysis.ipynb |
Energy ratio |
scalability-analisys.ipynb |
Strong-scaling plots: wall time, speedup and parallel efficiency vs. number of MPI processes. Includes Amdahl's law fit. |
All the notebooks expect the .csv data to be placed in the folder analysis/data/.
We would like to thank Prof. Michele Bucelli for its valuable advices during the execution of the project.
{ "Geometry": "[0.0, 1.0] x [0.0, 1.0]", // domain bounding box "Nel": "80", // elements per side (or "80, 60" for rectangular) "R": "1", // polynomial degree "T": "1.0", // final time "Theta": "0.5", // theta parameter (theta-method only) "Beta": "0.25", // beta parameter (Newmark only) "Gamma": "0.5", // gamma parameter (Newmark only) "Dt": "0.005", // time step "Save Solution": true, // write VTU output "Enable Logging": true, // write energy/error CSVs "Log Every": 10, // log frequency (0 = off) "Print Every": 10, // console output frequency "C": { "Function expression": "1.0", ... }, // wave speed c(x,y,t) "F": { "Function expression": "0.0", ... }, // forcing f(x,y,t) "U0": { "Function expression": "sin(pi*x)*sin(pi*y)", ... }, // initial displacement "V0": { "Function expression": "0.0", ... }, // initial velocity "G": { "Function expression": "0.0", ... }, // Dirichlet BC for u "DGDT": { "Function expression": "0.0", ... }, // time-derivative of g "Solution": { "Function expression": "...", ... } // exact solution (optional) }