Poisson Equation¶
To integrate a momentum field in time (specifically to enforce the incompressibility), we need to solve a Poisson equation:
where \(p\) and \(q\) are both defined at cell centers \(\left( \ccidx{i}, \ccidx{j}, \ccidx{k} \right)\).
Since directions except \(x\) are homogeneous (i.e., periodic boundary conditions are imposed and the grid sizes are equal), we consider the spectral representation (discrete backward Fourier transform) of \(p\):
where \(I\) is the imaginary unit \(\sqrt{-1}\). Note that the discrete forward Fourier transform yields
Since we have
the second-order derivatives in the Poisson equation can be reformulated as
where
which are computed here:
233double * tdm_l = NULL;
234double * tdm_u = NULL;
235tdm.get_l(*tdm_info, &tdm_l);
236tdm.get_u(*tdm_info, &tdm_u);
237const double * hxxf = domain->hxxf;
238const double * jdxf = domain->jdxf;
239const double * jdxc = domain->jdxc;
240for (int i = 1; i <= (int)tdm_sizes[0]; i++) {
241 // N.B. iterated from i = 1 to use macros
242 tdm_l[i-1] = 1. / JDXC(i ) * JDXF(i ) / HXXF(i ) / HXXF(i );
243 tdm_u[i-1] = 1. / JDXC(i ) * JDXF(i+1) / HXXF(i+1) / HXXF(i+1);
244}
Consequently, we obtain
By applying the discrete forward Fourier transform, we obtain
The resulting tri-diagonal matrix for each \(m\) and \(n\) is solved here:
546#if NDIMS == 2
547 for (size_t j = 0; j < jsize; j++) {
548 const double eval_y = eval_ys[j];
549 // set center diagonal components
550 for (size_t i = 0; i < isize; i++) {
551 tdm_c[i] =
552 - tdm_l[i]
553 - tdm_u[i]
554 + eval_y / hy / hy;
555 }
556 // boundary treatment (Neumann boundary condition)
557 tdm_c[ 0] += tdm_l[ 0];
558 tdm_c[isize - 1] += tdm_u[isize - 1];
559 // solve
560 tdm.solve(tdm_info, rhs + j * isize);
561 }
562#else
563 for (size_t k = 0; k < ksize; k++) {
564 const double eval_z = eval_zs[k];
565 for (size_t j = 0; j < jsize; j++) {
566 const double eval_y = eval_ys[j];
567 // set center diagonal components
568 for (size_t i = 0; i < isize; i++) {
569 tdm_c[i] =
570 - tdm_l[i]
571 - tdm_u[i]
572 + eval_y / hy / hy
573 + eval_z / hz / hz;
574 }
575 // boundary treatment (Neumann boundary condition)
576 tdm_c[ 0] += tdm_l[ 0];
577 tdm_c[isize - 1] += tdm_u[isize - 1];
578 // solve
579 tdm.solve(tdm_info, rhs + (k * jsize + j) * isize);
580 }
581 }
582#endif
The sinusoidal functions (wave numbers) are computed here:
377double * restrict * evals = &poisson_solver->eval_ys;
378size_t mysize = 0;
379size_t myoffs = 0;
380sdecomp.get_pencil_mysize(info, pencil, SDECOMP_YDIR, c_gl_sizes[SDECOMP_YDIR], &mysize);
381sdecomp.get_pencil_offset(info, pencil, SDECOMP_YDIR, c_gl_sizes[SDECOMP_YDIR], &myoffs);
382*evals = memory_calloc(mysize, sizeof(double));
383for (size_t local_n = 0; local_n < mysize; local_n++) {
384 const size_t global_n = local_n + myoffs;
385 (*evals)[local_n] =
386 - 4. * pow(
387 sin( g_pi * global_n / r_gl_sizes[SDECOMP_YDIR] ),
388 2.
389 );
390}
395double * restrict * evals = &poisson_solver->eval_zs;
396size_t mysize = 0;
397size_t myoffs = 0;
398sdecomp.get_pencil_mysize(info, pencil, SDECOMP_ZDIR, c_gl_sizes[SDECOMP_ZDIR], &mysize);
399sdecomp.get_pencil_offset(info, pencil, SDECOMP_ZDIR, c_gl_sizes[SDECOMP_ZDIR], &myoffs);
400*evals = memory_calloc(mysize, sizeof(double));
401for (size_t local_n = 0; local_n < mysize; local_n++) {
402 const size_t global_n = local_n + myoffs;
403 (*evals)[local_n] =
404 - 4. * pow(
405 sin( g_pi * global_n / r_gl_sizes[SDECOMP_ZDIR] ),
406 2.
407 );
408}
The overall procedure can be found here:
611int fluid_compute_potential (
612 const domain_t * domain,
613 const size_t rkstep,
614 const double dt,
615 fluid_t * fluid
616) {
617 static poisson_solver_t poisson_solver = {
618 .is_initialised = false,
619 };
620 // initialise Poisson solver
621 if (!poisson_solver.is_initialised) {
622 if (0 != init_poisson_solver(domain, &poisson_solver)) {
623 // failed to initialise Poisson solver
624 return 1;
625 }
626 }
627 // compute right-hand side of Poisson equation
628 // assigned to buf0
629 assign_input(domain, rkstep, dt, fluid, poisson_solver.buf0);
630 // transpose real x1pencil to y1pencil
631 // from buf0 to buf1
632 sdecomp.transpose.execute(
633 poisson_solver.r_transposer_x1_to_y1,
634 poisson_solver.buf0,
635 poisson_solver.buf1
636 );
637 // project y to wave space
638 // f(x, y) -> f(x, k_y)
639 // f(x, y, z) -> f(x, k_y, z)
640 // from buf1 to buf0
641 fftw_execute(poisson_solver.fftw_plan_y[0]);
642#if NDIMS == 2
643 // transpose complex y1pencil to x1pencil
644 // from buf0 to buf1
645 sdecomp.transpose.execute(
646 poisson_solver.c_transposer_y1_to_x1,
647 poisson_solver.buf0,
648 poisson_solver.buf1
649 );
650#else
651 // transpose complex y1pencil to z1pencil
652 // from buf0 to buf1
653 sdecomp.transpose.execute(
654 poisson_solver.c_transposer_y1_to_z1,
655 poisson_solver.buf0,
656 poisson_solver.buf1
657 );
658 // project z to wave space
659 // f(x, k_y, z) -> f(x, k_y, k_z)
660 // from buf1 to buf0
661 fftw_execute(poisson_solver.fftw_plan_z[0]);
662 // transpose complex z1pencil to x2pencil
663 // from buf0 to buf1
664 sdecomp.transpose.execute(
665 poisson_solver.c_transposer_z1_to_x2,
666 poisson_solver.buf0,
667 poisson_solver.buf1
668 );
669#endif
670 // solve linear systems in x direction
671 solve_linear_systems(domain, &poisson_solver);
672#if NDIMS == 2
673 // transpose complex x1pencil to y1pencil
674 // from buf1 to buf0
675 sdecomp.transpose.execute(
676 poisson_solver.c_transposer_x1_to_y1,
677 poisson_solver.buf1,
678 poisson_solver.buf0
679 );
680#else
681 // transpose complex x2pencil to z1pencil
682 // from buf1 to buf0
683 sdecomp.transpose.execute(
684 poisson_solver.c_transposer_x2_to_z1,
685 poisson_solver.buf1,
686 poisson_solver.buf0
687 );
688 // project z to physical space
689 // f(x, k_y, k_z) -> f(x, k_y, z)
690 // from buf0 to buf1
691 fftw_execute(poisson_solver.fftw_plan_z[1]);
692 // transpose complex z1pencil to y1pencil
693 // from buf1 to buf0
694 sdecomp.transpose.execute(
695 poisson_solver.c_transposer_z1_to_y1,
696 poisson_solver.buf1,
697 poisson_solver.buf0
698 );
699#endif
700 // project y to physical space
701 // f(x, k_y) -> f(x, y)
702 // f(x, k_y, z) -> f(x, y, z)
703 // from buf0 to buf1
704 fftw_execute(poisson_solver.fftw_plan_y[1]);
705 // transpose real y1pencil to x1pencil
706 // from buf1 to buf0
707 sdecomp.transpose.execute(
708 poisson_solver.r_transposer_y1_to_x1,
709 poisson_solver.buf1,
710 poisson_solver.buf0
711 );
712 // copy result to the original buffer
713 extract_output(domain, poisson_solver.buf0, fluid);
714 return 0;
715}