@@ -292,6 +292,174 @@ cmake --build build
292292There is also some examples in [ examples] [ examples-url ] directory, which can be
293293used 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+ 
460+
461+ The full code can be found from [examples](/examples/12_cahn_hilliard.cpp).
462+
295463## Troubleshooting and debugging
296464
297465Here'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