Vibrating string#
Solves the one-dimensional wave equation via finite differences, MPI version.
// solve the one-dimensional wave equation
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "mpi.h"
const int N = 1001; // total global number of grid points
const double L = 1, c = 1, dt = 0.001, t_end = 2.4;
enum { left_copy, right_copy };
// update string elongation
void string(const double *u, const double *u_old, double *u_new, int N, double eps) {
u_new[0] = u[0];
for (int i = 1; i < N - 1; ++i)
u_new[i] = eps * (u[i - 1] + u[i + 1]) + 2.0 * (1.0 - eps) * u[i] - u_old[i];
u_new[N - 1] = u[N - 1];
}
void *secure_malloc(size_t size) {
void *p = malloc(size);
if (p == NULL)
MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
return p;
}
// initial elongation
static inline double u_0(double x) {
if (x <= 0 || x >= L)
return 0;
return exp(-200.0 * (x - 0.5 * L) * (x - 0.5 * L));
}
// initial velocity
static inline double u_0_dt(double x) {
return 0.0;
}
int main(int argc, char *argv[]) {
int C_rank, C_size, *N_l, *N0_l;
double dx = L / (N - 1), eps = dt * dt * c * c / (dx * dx), *u = NULL, *u_l, *u_old_l,
*u_new_l, *u_temp;
MPI_Status statuses[4];
MPI_Request requests[4];
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &C_size);
MPI_Comm_rank(MPI_COMM_WORLD, &C_rank);
N_l = secure_malloc(C_size * sizeof(*N_l));
N0_l = secure_malloc(C_size * sizeof(*N0_l));
// calculate size and position of local grids
// the local grids include one mirror grid point at each end or the boundary condition
for (int i = 0; i < C_size; ++i) {
// number of local grid points is N_l[C_rank]
N_l[i] = (i + 1) * (N - 2) / C_size - i * (N - 2) / C_size + 2;
// position of local
N0_l[i] = i * (N - 2) / C_size;
}
u_old_l = secure_malloc((N_l[C_rank]) * sizeof(*u_old_l));
u_l = secure_malloc((N_l[C_rank]) * sizeof(*u_l));
u_new_l = secure_malloc((N_l[C_rank]) * sizeof(*u_new_l));
// 1st time step uses elongation and velocity
for (int i = 0; i < N_l[C_rank]; ++i) {
double x = (i + N0_l[C_rank]) * dx;
u_old_l[i] = u_0(x);
u_l[i] = 0.5 * eps * (u_0(x - dx) + u_0(x + dx)) + (1.0 - eps) * u_0(x) + dt * u_0_dt(x);
}
// solve wave equation by using elongation at current time and one step before
for (double t = 2 * dt; t <= t_end; t += dt) {
string(u_l, u_old_l, u_new_l, N_l[C_rank], eps);
MPI_Isend(&u_new_l[N_l[C_rank] - 2], 1, MPI_DOUBLE,
C_rank + 1 < C_size ? C_rank + 1 : MPI_PROC_NULL, right_copy, MPI_COMM_WORLD,
&requests[0]);
MPI_Isend(&u_new_l[1], 1, MPI_DOUBLE, C_rank - 1 >= 0 ? C_rank - 1 : MPI_PROC_NULL,
left_copy, MPI_COMM_WORLD, &requests[1]);
MPI_Irecv(&u_new_l[0], 1, MPI_DOUBLE, C_rank - 1 >= 0 ? C_rank - 1 : MPI_PROC_NULL,
right_copy, MPI_COMM_WORLD, &requests[2]);
MPI_Irecv(&u_new_l[N_l[C_rank] - 1], 1, MPI_DOUBLE,
C_rank + 1 < C_size ? C_rank + 1 : MPI_PROC_NULL, left_copy, MPI_COMM_WORLD,
&requests[3]);
MPI_Waitall(4, requests, statuses);
u_temp = u_old_l;
u_old_l = u_l;
u_l = u_new_l;
u_new_l = u_temp;
}
// exclude overlapping grid points
for (int i = 0; i < C_size; ++i) {
N_l[i] -= 2;
N0_l[i] += 1;
}
// gather and print data
if (C_rank == 0)
u = secure_malloc(N * sizeof(*u));
MPI_Gatherv(u_l + 1, N_l[C_rank], MPI_DOUBLE, u, N_l, N0_l, MPI_DOUBLE, 0, MPI_COMM_WORLD);
if (C_rank == 0) {
u[0] = u[N - 1] = 0; // boundary condition
for (int i = 0; i < N; ++i)
printf("%g\n", u[i]);
}
free(u);
free(u_old_l);
free(u_l);
free(u_new_l);
MPI_Finalize();
return EXIT_SUCCESS;
}
Solves the one-dimensional wave equation via finite differences, MPL version.
// solve the time-dependent one-dimensional wave equation
// via a finite difference discretization and explicit time stepping
#include <cstdlib>
#include <iostream>
#include <cmath>
#include <vector>
#include <mpl/mpl.hpp>
const int n{1001}; // total number of grid points
const double l{1}; // lengths of domain
const double c{1}; // speed of sound
const double dt{0.001}; // temporal step width
const double t_end{2.4}; // simulation time
enum class copy : int { left, right };
// update grid points
void string(const std::vector<double> &u, const std::vector<double> &u_old,
std::vector<double> &u_new, double eps) {
using size_type = std::vector<double>::size_type;
const size_type N{u.size()};
u_new[0] = u[0];
for (size_type i{1}; i < N - 1; ++i)
u_new[i] = eps * (u[i - 1] + u[i + 1]) + 2.0 * (1.0 - eps) * u[i] - u_old[i];
u_new[N - 1] = u[N - 1];
}
// initial elongation of string
inline double u_0(double x) {
if (x <= 0 or x >= l)
return 0;
return std::exp(-200.0 * (x - 0.5 * l) * (x - 0.5 * l));
}
// initial velocity of string
inline double u_0_dt([[maybe_unused]] double x) {
return 0.0;
}
int main() {
const double dx{l / (n - 1)}; // grid spacing
const double eps{dt * dt * c * c / (dx * dx)};
const mpl::communicator &comm_world{mpl::environment::comm_world()};
const int c_size{comm_world.size()};
const int c_rank{comm_world.rank()};
std::vector<int> n_l, n_0_l;
for (int i{0}; i < c_size; ++i) {
// number of local grid points of process i
n_l.push_back((i + 1) * (n - 2) / c_size - i * (n - 2) / c_size + 2);
// position of local grid of process i within the global grid
n_0_l.push_back(i * (n - 2) / c_size);
}
// grid data for times (t-dt), t and t+dt
std::vector<double> u_old_l(n_l[c_rank]);
std::vector<double> u_l(n_l[c_rank]);
std::vector<double> u_new_l(n_l[c_rank]);
// 1st propagation step uses current elongation and velocity
// calculate all grid points including overlapping border data
for (int i{0}; i < n_l[c_rank]; ++i) {
double x = (i + n_0_l[c_rank]) * dx;
u_old_l[i] = u_0(x);
u_l[i] = 0.5 * eps * (u_0(x - dx) + u_0(x + dx)) + (1.0 - eps) * u_0(x) + dt * u_0_dt(x);
}
// propagate
double t{2 * dt};
while (t <= t_end) {
// make one time step to get elongation
string(u_l, u_old_l, u_new_l, eps);
// update border data
mpl::irequest_pool r;
r.push(comm_world.isend(u_new_l[n_l[c_rank] - 2],
c_rank + 1 < c_size ? c_rank + 1 : mpl::proc_null, copy::right));
r.push(comm_world.isend(u_new_l[1], c_rank - 1 >= 0 ? c_rank - 1 : mpl::proc_null,
copy::left));
r.push(comm_world.irecv(u_new_l[0], c_rank - 1 >= 0 ? c_rank - 1 : mpl::proc_null,
copy::right));
r.push(comm_world.irecv(u_new_l[n_l[c_rank] - 1],
c_rank + 1 < c_size ? c_rank + 1 : mpl::proc_null, copy::left));
r.waitall();
std::swap(u_l, u_old_l);
std::swap(u_new_l, u_l);
t += dt;
}
// finally, gather all the data at rank 0 and print result
mpl::layouts<double> layouts;
for (int i{0}; i < c_size; ++i)
layouts.push_back(mpl::indexed_layout<double>({{n_l[i] - 2, n_0_l[i] + 1}}));
mpl::contiguous_layout<double> layout(n_l[c_rank] - 2);
if (c_rank == 0) {
std::vector<double> u(n, 0);
comm_world.gatherv(0, u_l.data() + 1, layout, u.data(), layouts);
for (int i{0}; i < n; ++i)
std::cout << dx * i << '\t' << u[i] << '\n';
} else
comm_world.gatherv(0, u_l.data() + 1, layout);
return EXIT_SUCCESS;
}