Skip to content

Commit 2c0a185

Browse files
committed
Update README.md: add example
1 parent 4d12e02 commit 2c0a185

File tree

1 file changed

+168
-4
lines changed

1 file changed

+168
-4
lines changed

README.md

Lines changed: 168 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,174 @@ cmake --build build
292292
There is also some examples in [examples][examples-url] directory, which can be
293293
used as a base for your own codes.
294294

295+
## Example: Cahill-Hilliard equation
296+
297+
The Cahn-Hilliard equation is a fundamental model in materials science used to
298+
describe the phase separation process in binary mixtures. It models how the
299+
concentration field evolves over time to minimize the free energy of the system.
300+
The equation is particularly useful in understanding the dynamics of spinodal
301+
decomposition and coarsening processes.
302+
303+
Spectral methods, combined with the Fast Fourier Transform (FFT), are highly
304+
efficient for solving partial differential equations (PDEs) like the
305+
Cahn-Hilliard equation. The FFT allows us to transform differential operators
306+
into algebraic ones in the frequency domain, significantly simplifying the
307+
computation. This approach is particularly advantageous for problems involving
308+
periodic boundary conditions and large-scale simulations, where the efficiency
309+
and accuracy of the FFT are paramount.
310+
311+
Exponential time integration is well-suited for stiff PDEs like the
312+
Cahn-Hilliard equation. Traditional explicit methods require very small time
313+
steps to maintain stability, which can be computationally expensive. Exponential
314+
integrators, however, handle the stiff linear part of the equation exactly,
315+
allowing for larger time steps without sacrificing stability. This makes the
316+
integration process more efficient and stable, especially for long-term
317+
simulations.
318+
319+
Starting with the Cahn-Hilliard equation:
320+
321+
$$
322+
\frac{\partial c}{\partial t} = D \nabla^{2} \left( c^{3} - c - \gamma \nabla^{2} c \right)
323+
$$
324+
325+
### Fourier Transform
326+
327+
Applying the Fourier transform to the equation converts the spatial differential
328+
operators into algebraic forms:
329+
330+
$$
331+
\frac{\partial \hat{c}}{\partial t} = D \left[ -k^2 \left( \hat{c^3} - \hat{c} - \gamma (-k^2 \hat{c}) \right) \right]
332+
$$
333+
334+
Simplifying the right-hand side:
335+
336+
$$
337+
\frac{\partial \hat{c}}{\partial t} = D \left[ -k^2 \hat{c^3} + k^2 \hat{c} + \gamma k^4 \hat{c} \right]
338+
$$
339+
340+
$$
341+
\frac{\partial \hat{c}}{\partial t} = D \left[ -k^2 \hat{c^3} + (k^2 + \gamma k^4) \hat{c} \right]
342+
$$
343+
344+
### Discretization in Time
345+
346+
Using an exponential integrator, we handle the linear part exactly and integrate
347+
the non-linear part explicitly:
348+
349+
$$
350+
\hat{c}(t + \Delta t) = \exp(DL \Delta t) \hat{c}(t) + \int_t^{t + \Delta t} \exp(DL (t + \Delta t - s)) (-Dk^2 \hat{c^3}(s)) \, \mathrm{d}s
351+
$$
352+
353+
Here, $L = k^2 + \gamma k^4$.
354+
355+
Assuming $\hat{c}$ is approximately constant over the small interval $\Delta t$,
356+
we approximate the integral:
357+
358+
$$
359+
\hat{c}(t + \Delta t) \approx \exp(D (k^2 + \gamma k^4) \Delta t) \hat{c}(t) + \left( \frac{ \exp(D (k^2 + \gamma k^4) \Delta t) - 1 }{D (k^2 + \gamma k^4)} \right) (-Dk^2 \hat{c^3}(t))
360+
$$
361+
362+
### Final Time-Stepping Formula
363+
364+
Combining the terms, we obtain the discrete update rule for $\hat{c}$:
365+
366+
$$
367+
\hat{c}_{n+1} = \exp(D (k^2 + \gamma k^4) \Delta t) \hat{c}_n + \frac{\exp(D (k^2 + \gamma k^4) \Delta t) - 1}{D (k^2 + \gamma k^4)} (-Dk^2 \hat{c^3}_n)
368+
$$
369+
370+
Simplify the coefficient for the non-linear term:
371+
372+
$$
373+
\hat{c}_{n+1} = \exp(D (k^2 + \gamma k^4) \Delta t) \hat{c}_n - \frac{\exp(D (k^2 + \gamma k^4) \Delta t) - 1}{k^2 + \gamma k^4} k^2 \hat{c^3}_n
374+
$$
375+
376+
### Linear and Non-Linear Operators
377+
378+
The linear and non-linear operators can be defined as:
379+
380+
- **Linear Operator ($ \text{opL} $)**:
381+
$$
382+
\text{opL} = \exp(D (k^2 + \gamma k^4) \Delta t)
383+
$$
384+
385+
- **Non-Linear Operator ($ \text{opN} $)**:
386+
$$
387+
\text{opN} = \frac{\exp(D (k^2 + \gamma k^4) \Delta t) - 1}{k^2 + \gamma k^4} k^2
388+
$$
389+
390+
These operators are used to update the concentration field $c$ in the
391+
Fourier domain efficiently, leveraging the FFT for computational efficiency.
392+
This method allows for stable and accurate integration of the Cahn-Hilliard
393+
equation over time, making it suitable for large-scale simulations of phase
394+
separation processes.
395+
396+
Below is the code snippet for the Cahn-Hilliard model in OpenPFC:
397+
398+
```cpp
399+
class CahnHilliard : public Model {
400+
private:
401+
std::vector<double> opL, opN, c; // Define operators and field c
402+
std::vector<std::complex<double>> c_F, c_NF; // Define (complex) psi
403+
double gamma = 1.0e-2; // Surface tension
404+
double D = 1.0; // Diffusion coefficient
405+
406+
public:
407+
void initialize(double dt) override {
408+
FFT &fft = get_fft();
409+
const Decomposition &decomp = get_decomposition();
410+
411+
// Allocate space for the main variable and it's fourier transform
412+
c.resize(fft.size_inbox());
413+
c_F.resize(fft.size_outbox());
414+
c_NF.resize(fft.size_outbox());
415+
opL.resize(fft.size_outbox());
416+
opN.resize(fft.size_outbox());
417+
add_real_field("concentration", c);
418+
419+
// prepare operators
420+
World w = get_world();
421+
std::array<int, 3> o_low = decomp.outbox.low;
422+
std::array<int, 3> o_high = decomp.outbox.high;
423+
size_t idx = 0;
424+
double pi = std::atan(1.0) * 4.0;
425+
double fx = 2.0 * pi / (w.dx * w.Lx);
426+
double fy = 2.0 * pi / (w.dy * w.Ly);
427+
double fz = 2.0 * pi / (w.dz * w.Lz);
428+
for (int k = o_low[2]; k <= o_high[2]; k++) {
429+
for (int j = o_low[1]; j <= o_high[1]; j++) {
430+
for (int i = o_low[0]; i <= o_high[0]; i++) {
431+
// Laplacian operator -k^2
432+
double ki = (i <= w.Lx / 2) ? i * fx : (i - w.Lx) * fx;
433+
double kj = (j <= w.Ly / 2) ? j * fy : (j - w.Ly) * fy;
434+
double kk = (k <= w.Lz / 2) ? k * fz : (k - w.Lz) * fz;
435+
double kLap = -(ki * ki + kj * kj + kk * kk);
436+
double L = kLap * (-D - D * gamma * kLap);
437+
opL[idx] = std::exp(L * dt);
438+
opN[idx] = (L != 0.0) ? (opL[idx] - 1.0) / L * kLap : 0.0;
439+
idx++;
440+
}
441+
}
442+
}
443+
}
444+
445+
void step(double) override {
446+
// Calculate cₙ₊₁ = opL * cₙ + opN * cₙ³
447+
FFT &fft = get_fft();
448+
fft.forward(c, c_F);
449+
for (auto &elem : c) elem = D * elem * elem * elem;
450+
fft.forward(c, c_NF);
451+
for (size_t i = 0; i < c_F.size(); i++) {
452+
c_F[i] = opL[i] * c_F[i] + opN[i] * c_NF[i];
453+
}
454+
fft.backward(c_F, c);
455+
}
456+
};
457+
```
458+
459+
![Cahn-Hilliard simulation](docs/img/cahn_hilliard.gif)
460+
461+
The full code can be found from [examples](/examples/12_cahn_hilliard.cpp).
462+
295463
## Troubleshooting and debugging
296464
297465
Here's some common problems and their solutions.
@@ -412,10 +580,6 @@ CHECK_AND_ABORT_IF_NANS(psi);
412580
413581
[tungsten-nan-check]: https://github.com/VTT-ProperTune/OpenPFC/blob/master/apps/tungsten.cpp#L220
414582
415-
## Examples
416-
417-
A bigger application example is Tungsten model. Todo.
418-
419583
## Citing
420584
421585
```bibtex

0 commit comments

Comments
 (0)