-
Notifications
You must be signed in to change notification settings - Fork 23
Open Boundaries #666
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open Boundaries #666
Changes from 53 commits
662947f
c61f4f8
1b44ecd
5b9dd1c
741c2e6
6a62d50
755252e
7997b27
8466220
fc7fccd
7281c99
272b402
8a13ccf
99875d8
244955c
fc392b5
79a5f19
e4afa2d
3ecb1a4
0fd899b
3d51ba5
ee50414
60d97cb
33e826f
d8c98d0
7b3446b
0e46aa0
762b237
936aaf5
6f82a70
e654a32
c3f7be6
a77a335
1368e59
94266c7
4424c86
d4856a8
043d5a5
7e04397
a13bce3
244b2ed
cbcc2e3
a2936da
f2ee319
8f4e3f9
f97af35
77b3e65
375b591
ee3ecfe
8d7e99a
ed39bb1
e04d164
6da3233
99c9ec5
b4e2858
332e2fd
d547415
f35927e
84b5613
9ad75d1
d6932f2
6ed1783
1aa044b
8bb6429
a4984c9
bd6964f
43b3fb6
02533a2
939b0b8
7033464
5418ccf
9a927b1
6bbedff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -146,6 +146,17 @@ Modeling ion motion is not yet supported by the explicit solver | |
| The small dst is quicker for simulations with :math:`\geq 511` transverse grid points. | ||
| The default is set accordingly. | ||
|
|
||
| * ``fields.extended_solve`` (`bool`) optional (default `0`) | ||
| Extends the area of the FFT Poisson solver to the ghost cells. This can reduce artefacts | ||
| originating from the boundary for long simulations. | ||
|
|
||
| * ``fields.open_boundary`` (`bool`) optional (default `0`) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for future extensibility, you could use the same syntax as in WarpX here:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, because right now there is now functionality to have different boundary conditions for different sides of the box (and no use in hipace?). Moreover, as I understand it, the four combinations of |
||
| Uses a Taylor approximation of the Greens function to solve the Poisson equations with | ||
| open boundary conditions. It's recommended to use this together with | ||
| `fields.extended_solve = true` and `geometry.is_periodic = false false false`. Not implemented | ||
AlexanderSinn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for the explicit Helmholtz solver. | ||
|
|
||
|
|
||
| Predictor-corrector loop parameters | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
| #include "fft_poisson_solver/FFTPoissonSolverPeriodic.H" | ||
| #include "fft_poisson_solver/FFTPoissonSolverDirichlet.H" | ||
| #include "Hipace.H" | ||
| #include "OpenBoundary.H" | ||
| #include "utils/HipaceProfilerWrapper.H" | ||
| #include "utils/Constants.H" | ||
| #include "particles/ShapeFactors.H" | ||
|
|
@@ -16,6 +17,8 @@ Fields::Fields (Hipace const* a_hipace) | |
| { | ||
| amrex::ParmParse ppf("fields"); | ||
| queryWithParser(ppf, "do_dirichlet_poisson", m_do_dirichlet_poisson); | ||
| queryWithParser(ppf, "extended_solve", m_extended_solve); | ||
| queryWithParser(ppf, "open_boundary", m_open_boundary); | ||
| } | ||
|
|
||
| void | ||
|
|
@@ -27,11 +30,25 @@ Fields::AllocData ( | |
| AMREX_ALWAYS_ASSERT_WITH_MESSAGE(slice_ba.size() == 1, | ||
| "Parallel field solvers not supported yet"); | ||
|
|
||
| // Need 1 extra guard cell transversally for transverse derivative | ||
| int nguards_xy = std::max(1, Hipace::m_depos_order_xy); | ||
| m_slices_nguards = {nguards_xy, nguards_xy, 0}; | ||
| // Poisson solver same size as domain, no ghost cells | ||
| m_poisson_nguards = {0, 0, 0}; | ||
| if (m_extended_solve) { | ||
| // Need 1 extra guard cell transversally for transverse derivative | ||
| int nguards_xy = (Hipace::m_depos_order_xy + 1) / 2 + 1; | ||
| m_slices_nguards = {nguards_xy, nguards_xy, 0}; | ||
| // poisson solver same size as fields | ||
| m_poisson_nguards = m_slices_nguards; | ||
| // one cell less for transverse derivative | ||
| m_exmby_eypbx_nguard = m_slices_nguards - amrex::IntVect{1, 1, 0}; | ||
| // cut off anything near edge of charge/current deposition | ||
| m_source_nguard = -m_slices_nguards; | ||
| } else { | ||
| // Need 1 extra guard cell transversally for transverse derivative | ||
| int nguards_xy = std::max(1, Hipace::m_depos_order_xy); | ||
| m_slices_nguards = {nguards_xy, nguards_xy, 0}; | ||
| // Poisson solver same size as domain, no ghost cells | ||
| m_poisson_nguards = {0, 0, 0}; | ||
| m_exmby_eypbx_nguard = {0, 0, 0}; | ||
| m_source_nguard = {0, 0, 0}; | ||
| } | ||
|
|
||
| for (int islice=0; islice<WhichSlice::N; islice++) { | ||
| m_slices[lev][islice].define( | ||
|
|
@@ -82,19 +99,12 @@ struct derivative_inner { | |
| // captured variables for GPU | ||
| amrex::Array4<amrex::Real const> array; | ||
| amrex::Real dx_inv; | ||
| int box_lo; | ||
| int box_hi; | ||
|
|
||
| // derivative of field in dir direction (x or y) | ||
| // the field is zero-extended such that this derivative can be accessed on the same box | ||
| AMREX_GPU_DEVICE amrex::Real operator() (int i, int j, int k) const noexcept { | ||
| constexpr bool is_x_dir = dir == Direction::x; | ||
| constexpr bool is_y_dir = dir == Direction::y; | ||
| const int ij_along_dir = is_x_dir * i + is_y_dir * j; | ||
| const bool lo_guard = ij_along_dir != box_lo; | ||
| const bool hi_guard = ij_along_dir != box_hi; | ||
| return (array(i+is_x_dir*hi_guard,j+is_y_dir*hi_guard,k)*hi_guard | ||
| -array(i-is_x_dir*lo_guard,j-is_y_dir*lo_guard,k)*lo_guard) * dx_inv; | ||
| return (array(i+is_x_dir,j+is_y_dir,k) - array(i-is_x_dir,j-is_y_dir,k)) * dx_inv; | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -112,8 +122,7 @@ struct derivative_inner<Direction::z> { | |
| } | ||
| }; | ||
|
|
||
| /** \brief derivative in x or y direction. Field is zero-extended by one cell such that this | ||
| * derivative can be accessed on the same box as the field */ | ||
| /** \brief derivative in x or y direction */ | ||
| template<int dir> | ||
| struct derivative { | ||
| // use brace initialization as constructor | ||
|
|
@@ -122,9 +131,7 @@ struct derivative { | |
|
|
||
| // use .array(mfi) like with amrex::MultiFab | ||
| derivative_inner<dir> array (amrex::MFIter& mfi) const { | ||
| amrex::Box bx = f_view[mfi].box(); | ||
| return derivative_inner<dir>{f_view.array(mfi), | ||
| 1._rt/(2._rt*geom.CellSize(dir)), bx.smallEnd(dir), bx.bigEnd(dir)}; | ||
| return derivative_inner<dir>{f_view.array(mfi), 1._rt/(2._rt*geom.CellSize(dir))}; | ||
| } | ||
| }; | ||
|
|
||
|
|
@@ -278,10 +285,16 @@ LinCombination (const amrex::IntVect box_grow, amrex::MultiFab dst, | |
| const auto src_a_array = src_a.array(mfi); | ||
| const auto src_b_array = src_b.array(mfi); | ||
| const amrex::Box bx = mfi.growntilebox(box_grow); | ||
| amrex::ParallelFor(bx, | ||
| const int box_i_lo = bx.smallEnd(Direction::x); | ||
| const int box_j_lo = bx.smallEnd(Direction::y); | ||
| const int box_i_hi = bx.bigEnd(Direction::x); | ||
| const int box_j_hi = bx.bigEnd(Direction::y); | ||
| amrex::ParallelFor(mfi.growntilebox(), | ||
| [=] AMREX_GPU_DEVICE(int i, int j, int k) noexcept | ||
| { | ||
| dst_array(i,j,k) = factor_a * src_a_array(i,j,k) + factor_b * src_b_array(i,j,k); | ||
| const bool inside = box_i_lo<=i && i<=box_i_hi && box_j_lo<=j && j<=box_j_hi; | ||
| dst_array(i,j,k) = | ||
| inside ? factor_a * src_a_array(i,j,k) + factor_b * src_b_array(i,j,k) : 0._rt; | ||
| }); | ||
| } | ||
| } | ||
|
|
@@ -493,27 +506,85 @@ void | |
| Fields::SetBoundaryCondition (amrex::Vector<amrex::Geometry> const& geom, const int lev, | ||
| std::string component, const int islice) | ||
| { | ||
| if (lev == 0) return; // keep lev==0 boundaries zero | ||
| HIPACE_PROFILE("Fields::SetBoundaryCondition()"); | ||
| constexpr int interp_order = 2; | ||
| if (lev == 0 && m_open_boundary) { | ||
| // Coarsest level: use Taylor expansion of the Green's function | ||
| // to get Dirichlet boundary conditions | ||
|
|
||
| const amrex::Real ref_ratio_z = Hipace::GetRefRatio(lev)[2]; | ||
| const amrex::Real islice_coarse = (islice + 0.5_rt) / ref_ratio_z; | ||
| const amrex::Real rel_z = islice_coarse - static_cast<int>(amrex::Math::floor(islice_coarse)); | ||
| amrex::MultiFab staging_area = getStagingArea(lev); | ||
| // Open Boundaries only work for lev0 with everything in one box | ||
AlexanderSinn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| amrex::FArrayBox& staging_area_fab = staging_area[0]; | ||
|
|
||
| auto solution_interp = interpolated_field_xyz<interp_order>{ | ||
| getField(lev-1, WhichSlice::This, component), | ||
| getField(lev-1, WhichSlice::Previous1, component), | ||
| rel_z, geom[lev-1]}; | ||
| amrex::MultiFab staging_area = getStagingArea(lev); | ||
| const auto arr_staging_area = staging_area_fab.array(); | ||
| const amrex::Box staging_box = staging_area_fab.box(); | ||
|
|
||
| for (amrex::MFIter mfi(staging_area, false); mfi.isValid(); ++mfi) | ||
| { | ||
| const auto arr_solution_interp = solution_interp.array(mfi); | ||
| const auto arr_staging_area = staging_area.array(mfi); | ||
| const amrex::Box fine_staging_box = staging_area[mfi].box(); | ||
| const amrex::Real poff_x = GetPosOffset(0, geom[lev], staging_box); | ||
| const amrex::Real poff_y = GetPosOffset(1, geom[lev], staging_box); | ||
| const amrex::Real dx = geom[lev].CellSize(0); | ||
| const amrex::Real dy = geom[lev].CellSize(1); | ||
| // scale factor cancels out for all multipole coefficients except the 0th, for wich it adds | ||
| // a constant therm to the potential | ||
AlexanderSinn marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const amrex::Real scale = 3._rt/std::sqrt( | ||
| pow<2>(geom[lev].ProbLength(0)) + pow<2>(geom[lev].ProbLength(1))); | ||
| const amrex::Real radius = amrex::min( | ||
| std::abs(geom[lev].ProbLo(0)), std::abs(geom[lev].ProbHi(0)), | ||
| std::abs(geom[lev].ProbLo(1)), std::abs(geom[lev].ProbHi(1))); | ||
| AMREX_ALWAYS_ASSERT_WITH_MESSAGE(radius > 0._rt, "The x=0, y=0 coordinate must be inside" | ||
| "the simulation box as it is used as the point of expansion for open boundaries"); | ||
| // ignore everything outside of 95% the min radius as the Taylor expansion only converges | ||
| // outside of a circular patch containing the sources, i.e. the sources can't be further | ||
| // from the center than the closest boundary as it would be the case in the corners | ||
| const amrex::Real cutoff_sq = pow<2>(0.95_rt * radius * scale); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't 95% arbitrary? Should it be e.g. min radius minus 3 cells or so?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It’s not about a fixed number of Cells, rather it’s to stay a safe distance away from the convergence radius of the Taylor expansion. |
||
| const amrex::Real dxdy_div_4pi = dx*dy/(4._rt * MathConst::pi); | ||
|
|
||
| MultipoleTuple coeff_tuple = | ||
| amrex::ParReduce(MultipoleReduceOpList{}, MultipoleReduceTypeList{}, | ||
| staging_area, m_source_nguard, | ||
| [=] AMREX_GPU_DEVICE (int /*box_num*/, int i, int j, int k) noexcept | ||
| { | ||
| const amrex::Real x = (i * dx + poff_x) * scale; | ||
| const amrex::Real y = (j * dy + poff_y) * scale; | ||
| if (x*x + y*y > cutoff_sq) return MultipoleTuple{}; //zero | ||
| amrex::Real s_v = arr_staging_area(i, j, k); | ||
| return GetMultipoleCoeffs(s_v, x, y); | ||
| } | ||
| ); | ||
|
|
||
| if (component == "Ez" || component == "Bz") { | ||
| // Because Ez and Bz only have transverse derivatives of currents as sources, the | ||
| // integral over the whole box is zero, meaning they have no physical monopole component | ||
| amrex::get<0>(coeff_tuple) = 0._rt; | ||
| } | ||
|
|
||
| SetDirichletBoundaries(arr_staging_area, staging_box, geom[lev], | ||
| [=] AMREX_GPU_DEVICE (amrex::Real x, amrex::Real y) noexcept | ||
| { | ||
| return dxdy_div_4pi*GetFieldMultipole(coeff_tuple, x*scale, y*scale); | ||
| } | ||
| ); | ||
|
|
||
| SetDirichletBoundaries(arr_staging_area, fine_staging_box, geom[lev], arr_solution_interp); | ||
| } else if (lev == 1) { | ||
| // Fine level: interpolate solution from coarser level to get Dirichlet boundary conditions | ||
| constexpr int interp_order = 2; | ||
|
|
||
| const amrex::Real ref_ratio_z = Hipace::GetRefRatio(lev)[2]; | ||
| const amrex::Real islice_coarse = (islice + 0.5_rt) / ref_ratio_z; | ||
| const amrex::Real rel_z = islice_coarse-static_cast<int>(amrex::Math::floor(islice_coarse)); | ||
|
|
||
| auto solution_interp = interpolated_field_xyz<interp_order>{ | ||
| getField(lev-1, WhichSlice::This, component), | ||
| getField(lev-1, WhichSlice::Previous1, component), | ||
| rel_z, geom[lev-1]}; | ||
| amrex::MultiFab staging_area = getStagingArea(lev); | ||
|
|
||
| for (amrex::MFIter mfi(staging_area, false); mfi.isValid(); ++mfi) | ||
| { | ||
| const auto arr_solution_interp = solution_interp.array(mfi); | ||
| const auto arr_staging_area = staging_area.array(mfi); | ||
| const amrex::Box fine_staging_box = staging_area[mfi].box(); | ||
|
|
||
| SetDirichletBoundaries(arr_staging_area,fine_staging_box,geom[lev],arr_solution_interp); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -588,17 +659,19 @@ Fields::SolvePoissonExmByAndEypBx (amrex::Vector<amrex::Geometry> const& geom, | |
| InterpolateFromLev0toLev1(geom, lev, "rho", islice, m_poisson_nguards, -m_slices_nguards); | ||
|
|
||
| // calculating the right-hand side 1/episilon0 * -(rho-Jz/c) | ||
| LinCombination(m_poisson_nguards, getStagingArea(lev), | ||
| LinCombination(m_source_nguard, getStagingArea(lev), | ||
| 1._rt/(phys_const.c*phys_const.ep0), getField(lev, WhichSlice::This, "jz"), | ||
| -1._rt/(phys_const.ep0), getField(lev, WhichSlice::This, "rho")); | ||
|
|
||
| SetBoundaryCondition(geom, lev, "Psi", islice); | ||
| m_poisson_solver[lev]->SolvePoissonEquation(lhs); | ||
|
|
||
| /* ---------- Transverse FillBoundary Psi ---------- */ | ||
| amrex::ParallelContext::push(m_comm_xy); | ||
| lhs.FillBoundary(geom[lev].periodicity()); | ||
| amrex::ParallelContext::pop(); | ||
| if (!m_extended_solve) { | ||
| /* ---------- Transverse FillBoundary Psi ---------- */ | ||
| amrex::ParallelContext::push(m_comm_xy); | ||
| lhs.FillBoundary(geom[lev].periodicity()); | ||
| amrex::ParallelContext::pop(); | ||
| } | ||
|
|
||
| InterpolateFromLev0toLev1(geom, lev, "Psi", islice, m_slices_nguards, m_poisson_nguards); | ||
|
|
||
|
|
@@ -615,7 +688,7 @@ Fields::SolvePoissonExmByAndEypBx (amrex::Vector<amrex::Geometry> const& geom, | |
| const amrex::Array4<amrex::Real> array_EypBx = f_EypBx.array(mfi); | ||
| const amrex::Array4<amrex::Real const> array_Psi = f_Psi.array(mfi); | ||
| // number of ghost cells where ExmBy and EypBx are calculated is 0 for now | ||
| const amrex::Box bx = mfi.growntilebox(amrex::IntVect{0, 0, 0}); | ||
| const amrex::Box bx = mfi.growntilebox(m_exmby_eypbx_nguard); | ||
| const amrex::Real dx_inv = 1._rt/(2._rt*geom[lev].CellSize(Direction::x)); | ||
| const amrex::Real dy_inv = 1._rt/(2._rt*geom[lev].CellSize(Direction::y)); | ||
|
|
||
|
|
@@ -643,7 +716,7 @@ Fields::SolvePoissonEz (amrex::Vector<amrex::Geometry> const& geom, const int le | |
|
|
||
| // Right-Hand Side for Poisson equation: compute 1/(episilon0 *c0 )*(d_x(jx) + d_y(jy)) | ||
| // from the slice MF, and store in the staging area of poisson_solver | ||
| LinCombination(m_poisson_nguards, getStagingArea(lev), | ||
| LinCombination(m_source_nguard, getStagingArea(lev), | ||
| 1._rt/(phys_const.ep0*phys_const.c), | ||
| derivative<Direction::x>{getField(lev, WhichSlice::This, "jx"), geom[lev]}, | ||
| 1._rt/(phys_const.ep0*phys_const.c), | ||
|
|
@@ -667,7 +740,7 @@ Fields::SolvePoissonBx (amrex::MultiFab& Bx_iter, amrex::Vector<amrex::Geometry> | |
|
|
||
| // Right-Hand Side for Poisson equation: compute -mu_0*d_y(jz) from the slice MF, | ||
| // and store in the staging area of poisson_solver | ||
| LinCombination(m_poisson_nguards, getStagingArea(lev), | ||
| LinCombination(m_source_nguard, getStagingArea(lev), | ||
| -phys_const.mu0, | ||
| derivative<Direction::y>{getField(lev, WhichSlice::This, "jz"), geom[lev]}, | ||
| phys_const.mu0, | ||
|
|
@@ -692,7 +765,7 @@ Fields::SolvePoissonBy (amrex::MultiFab& By_iter, amrex::Vector<amrex::Geometry> | |
|
|
||
| // Right-Hand Side for Poisson equation: compute mu_0*d_x(jz) from the slice MF, | ||
| // and store in the staging area of poisson_solver | ||
| LinCombination(m_poisson_nguards, getStagingArea(lev), | ||
| LinCombination(m_source_nguard, getStagingArea(lev), | ||
| phys_const.mu0, | ||
| derivative<Direction::x>{getField(lev, WhichSlice::This, "jz"), geom[lev]}, | ||
| -phys_const.mu0, | ||
|
|
@@ -719,7 +792,7 @@ Fields::SolvePoissonBz (amrex::Vector<amrex::Geometry> const& geom, const int le | |
|
|
||
| // Right-Hand Side for Poisson equation: compute mu_0*(d_y(jx) - d_x(jy)) | ||
| // from the slice MF, and store in the staging area of m_poisson_solver | ||
| LinCombination(m_poisson_nguards, getStagingArea(lev), | ||
| LinCombination(m_source_nguard, getStagingArea(lev), | ||
| phys_const.mu0, | ||
| derivative<Direction::y>{getField(lev, WhichSlice::This, "jx"), geom[lev]}, | ||
| -phys_const.mu0, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.