In the previous example, a simple diffusion model was implemented. The implementation was done at a rather low level for demonstration reasons and it had a few flaws. First, time stepping should be performed at a higher level. Hard-coding the initial condition inside the model also does not follow the good implementation practices of modular code. Later, we also want, for example, boundary conditions or to write the results of the simulation to the hard disk. These are all examples of things that basically should not be implemented in the model, because the model mainly includes physics. We want to use initial conditions and boundary conditions more widely in more different models.
This example introduces a few new classes: Time, Simulator, and FieldModifier. The responsibility of the Time object is to take care of the time step and to tell when the results of the calculation should be saved. The Simulator class combines model, time, initial conditions, and boundary conditions, being a higher-level abstraction of computation. The initial condition is implemented by inheriting the class FieldModifier. Initial conditions and boundary conditions can be implemented in the same class, therefore a slightly more generic name for the initial condition class.
#include <array>
#include <iostream>
#include <limits>
#include <memory>
#include <vector>
using namespace pfc;
private:
double D = 1.0;
public:
const FFT &fft =
m.get_fft();
std::vector<double> &field =
m.get_real_field(
"psi");
Int3
low = get_inbox(fft).low;
Int3
high = get_inbox(fft).high;
if (
m.is_rank0()) std::cout <<
"Create initial condition" << std::endl;
auto spacing = get_spacing(
w);
}
}
}
}
};
using Model::Model;
private:
std::vector<double> opL, psi;
std::vector<std::complex<double>> psi_F;
double psi_min = 0.0, psi_max = 1.0;
public:
double get_psi_min() const { return psi_min; }
double get_psi_max() const { return psi_max; }
void allocate() {
if (
is_rank0()) std::cout <<
"Allocate space" << std::endl;
psi.resize(fft.size_inbox());
psi_F.resize(fft.size_outbox());
opL.resize(fft.size_outbox());
add_real_field("psi", psi);
}
void prepare_operators(
double dt) {
auto &fft = get_fft();
std::array<int, 3>
low = get_outbox(fft).low;
std::array<int, 3>
high = get_outbox(fft).high;
if (
is_rank0()) std::cout <<
"Prepare operators" << std::endl;
double fx = 2.0 * constants::pi / (spacing[0] * size[0]);
double fy = 2.0 * constants::pi / (spacing[1] * size[1]);
double fz = 2.0 * constants::pi / (spacing[2] * size[2]);
double ki = (
i <= size[0] / 2) ?
i *
fx : (
i - size[0]) *
fx;
double kj = (
j <= size[1] / 2) ?
j *
fy : (
j - size[1]) *
fy;
double kk = (
k <= size[2] / 2) ?
k *
fz : (
k - size[2]) *
fz;
}
}
}
}
allocate();
}
void step(
double)
override {
fft.forward(psi, psi_F);
for (
int k = 0,
N = psi_F.size();
k <
N;
k++) psi_F[
k] = opL[
k] * psi_F[
k];
fft.backward(psi_F, psi);
}
double local_min = std::numeric_limits<double>::max();
double local_max = std::numeric_limits<double>::min();
};
}
};
if (!
s.is_rank0())
return;
int n =
s.get_increment();
std::cout <<
"n = " <<
n <<
", t = " <<
t <<
", min = " << min <<
", max = " << max
<< std::endl;
}
}
}
void run() {
double h = 2.0 * constants::pi / 8.0;
std::array<int, 3> dimensions = {
L,
L,
L};
auto decomp = decomposition::create(world, 1);
auto fft = fft::create(
decomp);
double t1 = 0.5874010519681994;
simulator.add_initial_conditions(std::make_unique<GaussianIC>());
if (std::abs(
model.get_psi_max() - 0.5) < 0.01) {
std::cout << "Test pass!" << std::endl;
} else {
std::cerr << "Test failed!" << std::endl;
}
}
}
std::cout << std::fixed;
std::cout.precision(12);
run();
}
Definition 04_diffusion_model.cpp:64
void initialize(double dt) override
Initialize the diffusion model.
Definition 04_diffusion_model.cpp:83
void step(double) override
The actual time stepping function.
Definition 04_diffusion_model.cpp:174
void find_minmax()
A simple MPI communication example.
Definition 04_diffusion_model.cpp:190
Custom field modifier: Gaussian initial condition.
Definition 05_simulator.cpp:44
void apply(Model &m, double t) override
Apply the field modification to the model (pure virtual)
Definition 05_simulator.cpp:49
Definition field_modifier.hpp:240
The Model class represents the physics model for simulations in OpenPFC.
Definition model.hpp:95
bool is_rank0() const noexcept
Check if current MPI rank is 0.
Definition model.hpp:175
const World & get_world() const noexcept
Get the decomposition object associated with the model.
Definition model.hpp:191
The Simulator class is responsible for running the simulation of the model.
Definition simulator.hpp:63
Mathematical and physical constants.
const Real3 & get_spacing(const CartesianCS &cs) noexcept
Get the spacing of the coordinate system.
Definition csys.hpp:252
Domain decomposition for parallel MPI simulations.
Factory functions for creating domain decompositions.
Fast Fourier Transform interface for spectral methods.
Base class for initial conditions and boundary conditions.
Physics model abstraction for phase-field simulations.
Simulation orchestration and time integration loop.
FFT class for distributed-memory parallel Fourier transforms.
Definition fft.hpp:248
Represents the global simulation domain (the "world").
Definition world.hpp:91
World class definition and unified interface.