Loading...
Searching...
No Matches
conversion.hpp
Go to the documentation of this file.
1
2#pragma once
9#include "linalg/vector.hpp"
10#include "linalg/vector_fv.hpp"
11#include "linalg/vector_q1.hpp"
12
13namespace terra::fv::hex {
14
15template < typename T, typename ScalarType, typename GridScalarType >
16concept FVProjectionFunctor = requires( const T& self, const dense::Vec< GridScalarType, 3 >& x ) {
17 { self.operator()( x ) } -> std::same_as< ScalarType >;
18};
19
20/// @brief L2 projection of an analytical function into a finite volume function.
21///
22/// Use this function if you want to represent an analytical function with a finite volume function.
23///
24/// Given some function \f$u = u(x)\f$ (as a @ref FVProjectionFunctor), this function computes for every finite volume
25/// cell \f$K\f$ the value
26/// \f[
27/// u_K = \frac{1}{|K|} \int_K u(x) \ dx
28/// \f]
29/// with
30/// \f[
31/// |K| = \int_K 1 \ dx
32/// \f]
33/// and writes \f$u_K\f$ into the respective finite volume cell.
34///
35/// @note The `operator()` method of the functor must be annotated with `KOKKOS_INLINE_FUNCTION`. Otherwise, this might
36/// not work on GPUs.
37///
38/// @param dst [out] finite volume function that is being written to
39/// @param src functor that implements the @ref FVProjectionFunctor concept
40/// @param coords_shell coords of the unit shell
41/// @param coords_radii radii of all shells
42template < typename ScalarType, typename GridScalarType, FVProjectionFunctor< ScalarType, GridScalarType > Functor >
45 const Functor& src,
48{
50 Kokkos::parallel_for(
51 "l2_project_into_fv_function",
52 Kokkos::MDRangePolicy(
53 { 0, 1, 1, 1 },
54 { fv_grid.extent( 0 ), fv_grid.extent( 1 ) - 1, fv_grid.extent( 2 ) - 1, fv_grid.extent( 3 ) - 1 } ),
55 KOKKOS_LAMBDA(
56 const int local_subdomain_id, const int hex_cell_x, const int hex_cell_y, const int hex_cell_r ) {
57 constexpr auto num_quad_points = fe::wedge::quadrature::quad_felippa_3x2_num_quad_points;
58 dense::Vec< ScalarType, 3 > quad_points[num_quad_points];
59 ScalarType quad_weights[num_quad_points];
62
63 ScalarType volume = 0.0;
64 ScalarType integral = 0.0;
65
69 wedge_phy_surf, coords_shell, local_subdomain_id, hex_cell_x - 1, hex_cell_y - 1 );
70
71 const auto r_1 = coords_radii( local_subdomain_id, hex_cell_r - 1 );
72 const auto r_2 = coords_radii( local_subdomain_id, hex_cell_r );
73
74 for ( int wedge = 0; wedge < fe::wedge::num_wedges_per_hex_cell; ++wedge )
75 {
76 for ( int q = 0; q < num_quad_points; ++q )
77 {
78 const auto J = fe::wedge::jac( wedge_phy_surf[wedge], r_1, r_2, quad_points[q] );
79 const auto det = J.det();
80 const auto abs_det = Kokkos::abs( det );
81 const auto Jw = abs_det * quad_weights[q];
82 volume += Jw;
83
84 const auto x = fe::wedge::forward_map(
85 wedge_phy_surf[wedge][0],
86 wedge_phy_surf[wedge][1],
87 wedge_phy_surf[wedge][2],
88 r_1,
89 r_2,
90 quad_points[q]( 0 ),
91 quad_points[q]( 1 ),
92 quad_points[q]( 2 ) );
93
94 integral += src( x ) * Jw;
95 }
96 }
97
98 fv_grid( local_subdomain_id, hex_cell_x, hex_cell_y, hex_cell_r ) = integral / volume;
99 } );
100}
101
102/// @brief L2 projection from a finite volume function into a Q1 (wedge) finite element function.
103///
104/// Use this function if you want to convert from the finite volume to the finite element space.
105///
106/// Given some finite volume function \f$u_h^{FV}\f$ this function solves
107/// \f[
108/// \int_\Omega u_h^{FE} v \ d\Omega = \int_\Omega u_h^{FV} v \ d\Omega, \quad \forall v \in V_h
109/// \f]
110/// for \f$u_h^{FE}\f$. In matrix form:
111/// \f[
112/// M u^{FE} = b
113/// \f]
114/// where \f$M\f$ is the finite element mass matrix and
115/// \f[
116/// b_i = \int_\Omega u^{FV}(x) \, \phi_i(x) \ dx = \sum_K u^{FV}_K \int_\Omega \phi_i(x) \ dx.
117/// \f]
118///
119/// Internally, this function
120/// 1. sets up \f$b\f$ locally
121/// 2. communicates \f$b\f$ to complete the assembly
122/// 3. solves the mass matrix system with CG
123///
124/// @param dst [out] finite element function that is the solution of the mass matrix system
125/// @param src finite volume vector to convert
126/// @param domain distributed domain (required for communication)
127/// @param coords_shell coords of the unit shell
128/// @param coords_radii radii of all shells
129/// @param tmps at least 5 tmp vectors (required for \f$b\f$ and the CG solver)
130template < typename ScalarType, typename GridScalarType >
134 const grid::shell::DistributedDomain& domain,
137 std::vector< linalg::VectorQ1Scalar< ScalarType > >& tmps )
138{
139 if ( tmps.size() < 5 )
140 {
141 Kokkos::abort( "At least 5 tmp vectors required." );
142 }
143
144 auto b = tmps[4];
145
146 linalg::assign( b, 0.0 );
147
149 grid::Grid4DDataScalar< ScalarType > b_grid = b.grid_data();
150
151 Kokkos::parallel_for(
152 "l2_project_fv_to_fe_rhs_assembly",
153 Kokkos::MDRangePolicy(
154 { 0, 1, 1, 1 },
155 { fv_grid.extent( 0 ), fv_grid.extent( 1 ) - 1, fv_grid.extent( 2 ) - 1, fv_grid.extent( 3 ) - 1 } ),
156 KOKKOS_LAMBDA(
157 const int local_subdomain_id, const int hex_cell_x, const int hex_cell_y, const int hex_cell_r ) {
158 const auto hex_cell_x_no_gl = hex_cell_x - 1;
159 const auto hex_cell_y_no_gl = hex_cell_y - 1;
160 const auto hex_cell_r_no_gl = hex_cell_r - 1;
161
162 constexpr auto num_quad_points = fe::wedge::quadrature::quad_felippa_3x2_num_quad_points;
163 dense::Vec< ScalarType, 3 > quad_points[num_quad_points];
164 ScalarType quad_weights[num_quad_points];
167
171 wedge_phy_surf, coords_shell, local_subdomain_id, hex_cell_x - 1, hex_cell_y - 1 );
172
173 const auto r_1 = coords_radii( local_subdomain_id, hex_cell_r - 1 );
174 const auto r_2 = coords_radii( local_subdomain_id, hex_cell_r );
175
176 const auto fv_value = fv_grid( local_subdomain_id, hex_cell_x, hex_cell_y, hex_cell_r );
177
179
180 for ( int wedge = 0; wedge < fe::wedge::num_wedges_per_hex_cell; ++wedge )
181 {
182 for ( int i = 0; i < fe::wedge::num_nodes_per_wedge; ++i )
183 {
184 for ( int q = 0; q < num_quad_points; ++q )
185 {
186 const auto J = fe::wedge::jac( wedge_phy_surf[wedge], r_1, r_2, quad_points[q] );
187 const auto det = J.det();
188 const auto abs_det = Kokkos::abs( det );
189 const auto shape_i_q = fe::wedge::shape( i, quad_points[q] );
190
191 b_local[wedge]( i ) += shape_i_q * abs_det * quad_weights[q];
192 }
193
194 b_local[wedge]( i ) *= fv_value;
195 }
196 }
197
199 b_grid, local_subdomain_id, hex_cell_x_no_gl, hex_cell_y_no_gl, hex_cell_r_no_gl, b_local );
200 } );
201
202 communication::shell::send_recv( domain, b_grid );
203
205 Mass M( domain, coords_shell, coords_radii );
206
208 linalg::solvers::IterativeSolverParameters( 1000, 1e-12, 1e-12 ), nullptr, tmps );
209
210 linalg::solvers::solve( solver, M, dst, b );
211}
212
213/// @brief Bound-preserving FV-to-FE transfer via a lumped mass matrix.
214///
215/// Computes, for every Q1 node \f$i\f$,
216/// \f[
217/// u_i^{FE} = \frac{b_i}{D_i},
218/// \quad
219/// b_i = \sum_K u_K^{FV} \int_K \phi_i \, dx,
220/// \quad
221/// D_i = \sum_K \int_K \phi_i \, dx
222/// \f]
223/// where \f$\phi_i\f$ are the Q1 wedge shape functions. Because every shape
224/// function is non-negative and the shape functions form a partition of unity,
225/// \f$u_i^{FE}\f$ is a convex combination of the surrounding FV cell values.
226/// Consequently this projection is **monotone**: if \f$u^{FV} \in [a, b]\f$
227/// then \f$u^{FE} \in [a, b]\f$, with no Gibbs-type over- or undershoots.
228///
229/// Compare with @ref l2_project_fv_to_fe which uses a consistent mass matrix
230/// and solves a global linear system. The lumped variant is cheaper (no
231/// solver, no MPI reduction for convergence) but slightly less L2-accurate in
232/// smooth cases. It is the preferred choice when bound-preservation matters
233/// (e.g.\ compositional fields, density anomalies that feed directly into
234/// buoyancy).
235///
236/// @param dst [out] FE scalar field receiving the projected values.
237/// @param src FV scalar field to project.
238/// @param domain Distributed domain (required for MPI ghost communication).
239/// @param coords_shell Lateral node coordinates of the unit-sphere surface.
240/// @param coords_radii Radial shell radii.
241/// @param tmps At least 2 temporary Q1 vectors (used for \f$b\f$ and \f$D\f$).
242template < typename ScalarType, typename GridScalarType >
246 const grid::shell::DistributedDomain& domain,
249 std::vector< linalg::VectorQ1Scalar< ScalarType > >& tmps )
250{
251 if ( tmps.size() < 2 )
252 {
253 Kokkos::abort( "At least 2 tmp vectors required." );
254 }
255
256 auto b = tmps[0]; // numerator: sum_K u_K * integral(phi_i, K)
257 auto D = tmps[1]; // denominator: sum_K integral(phi_i, K) (lumped diagonal)
258
259 linalg::assign( b, ScalarType( 0 ) );
260 linalg::assign( D, ScalarType( 0 ) );
261
263 grid::Grid4DDataScalar< ScalarType > b_grid = b.grid_data();
264 grid::Grid4DDataScalar< ScalarType > d_grid = D.grid_data();
265
266 Kokkos::parallel_for(
267 "l2_project_fv_to_fe_lumped_assembly",
268 Kokkos::MDRangePolicy(
269 { 0, 1, 1, 1 },
270 { fv_grid.extent( 0 ), fv_grid.extent( 1 ) - 1, fv_grid.extent( 2 ) - 1, fv_grid.extent( 3 ) - 1 } ),
271 KOKKOS_LAMBDA(
272 const int local_subdomain_id, const int hex_cell_x, const int hex_cell_y, const int hex_cell_r ) {
273 const auto hex_cell_x_no_gl = hex_cell_x - 1;
274 const auto hex_cell_y_no_gl = hex_cell_y - 1;
275 const auto hex_cell_r_no_gl = hex_cell_r - 1;
276
277 constexpr auto num_quad_points = fe::wedge::quadrature::quad_felippa_3x2_num_quad_points;
278 dense::Vec< ScalarType, 3 > quad_points[num_quad_points];
279 ScalarType quad_weights[num_quad_points];
282
286 wedge_phy_surf, coords_shell, local_subdomain_id, hex_cell_x - 1, hex_cell_y - 1 );
287
288 const auto r_1 = coords_radii( local_subdomain_id, hex_cell_r - 1 );
289 const auto r_2 = coords_radii( local_subdomain_id, hex_cell_r );
290
291 const auto fv_value = fv_grid( local_subdomain_id, hex_cell_x, hex_cell_y, hex_cell_r );
292
293 // d_local accumulates integral(phi_i, K); b_local = fv_value * d_local
296
297 for ( int wedge = 0; wedge < fe::wedge::num_wedges_per_hex_cell; ++wedge )
298 {
299 for ( int i = 0; i < fe::wedge::num_nodes_per_wedge; ++i )
300 {
301 for ( int q = 0; q < num_quad_points; ++q )
302 {
303 const auto J = fe::wedge::jac( wedge_phy_surf[wedge], r_1, r_2, quad_points[q] );
304 const auto abs_det = Kokkos::abs( J.det() );
305 const auto phi_i_Jw = fe::wedge::shape( i, quad_points[q] ) * abs_det * quad_weights[q];
306
307 d_local[wedge]( i ) += phi_i_Jw;
308 }
309
310 b_local[wedge]( i ) = fv_value * d_local[wedge]( i );
311 }
312 }
313
315 b_grid, local_subdomain_id, hex_cell_x_no_gl, hex_cell_y_no_gl, hex_cell_r_no_gl, b_local );
317 d_grid, local_subdomain_id, hex_cell_x_no_gl, hex_cell_y_no_gl, hex_cell_r_no_gl, d_local );
318 } );
319
320 communication::shell::send_recv( domain, b_grid );
321 communication::shell::send_recv( domain, d_grid );
322
323 // dst = b / D (element-wise)
324 linalg::assign( dst, b );
326 linalg::scale_in_place( dst, D );
327}
328
329/// @brief L2 projection from a Q1 finite element function into a finite volume function.
330///
331/// For each FV cell \f$K\f$ computes the exact volume-weighted cell average of the Q1 field:
332/// \f[
333/// u_K^{FV} = \frac{1}{|K|} \int_K u_h^{FE}(x) \, dx
334/// = \frac{1}{|K|} \int_K \sum_i u_i \, \phi_i(\xi(x)) \, dx
335/// \f]
336/// where \f$\phi_i\f$ are the wedge Q1 shape functions and \f$u_i\f$ are the Q1 nodal values
337/// surrounding the cell. The integral is evaluated with the same Felippa quadrature rule
338/// used throughout the codebase, so the result is consistent with `l2_project_fv_to_fe`.
339///
340/// Ghost layers of `dst` are populated via MPI exchange on exit so the result is immediately
341/// usable as input to FCT kernels.
342///
343/// @param dst [out] FV scalar field.
344/// @param src Q1 finite element scalar field (read-only).
345/// @param domain Distributed domain (used for MPI ghost-layer exchange).
346/// @param coords_shell Lateral node coordinates of the unit-sphere surface.
347/// @param coords_radii Radial shell radii.
348template < typename ScalarType, typename GridScalarType >
352 const grid::shell::DistributedDomain& domain,
354 const grid::Grid2DDataScalar< GridScalarType >& coords_radii )
355{
358
359 Kokkos::parallel_for(
360 "l2_project_fe_to_fv",
361 Kokkos::MDRangePolicy(
362 { 0, 1, 1, 1 },
363 { fv_grid.extent( 0 ), fv_grid.extent( 1 ) - 1, fv_grid.extent( 2 ) - 1, fv_grid.extent( 3 ) - 1 } ),
364 KOKKOS_LAMBDA(
365 const int local_subdomain_id, const int hex_cell_x, const int hex_cell_y, const int hex_cell_r ) {
366 // Q1 cell coordinates (0-indexed, no ghost-layer offset).
367 const auto x_no_gl = hex_cell_x - 1;
368 const auto y_no_gl = hex_cell_y - 1;
369 const auto r_no_gl = hex_cell_r - 1;
370
371 constexpr auto num_quad_points = fe::wedge::quadrature::quad_felippa_3x2_num_quad_points;
372 dense::Vec< ScalarType, 3 > quad_points[num_quad_points];
373 ScalarType quad_weights[num_quad_points];
376
377 // Gather Q1 nodal values for the two wedges of this hex cell.
380 local_u, local_subdomain_id, x_no_gl, y_no_gl, r_no_gl, q1_grid );
381
385 wedge_phy_surf, coords_shell, local_subdomain_id, x_no_gl, y_no_gl );
386
387 const auto r_1 = coords_radii( local_subdomain_id, r_no_gl );
388 const auto r_2 = coords_radii( local_subdomain_id, hex_cell_r );
389
390 ScalarType volume = ScalarType( 0 );
391 ScalarType integral = ScalarType( 0 );
392
393 for ( int wedge = 0; wedge < fe::wedge::num_wedges_per_hex_cell; ++wedge )
394 {
395 for ( int q = 0; q < num_quad_points; ++q )
396 {
397 const auto J = fe::wedge::jac( wedge_phy_surf[wedge], r_1, r_2, quad_points[q] );
398 const auto abs_det = Kokkos::abs( J.det() );
399 const auto Jw = abs_det * quad_weights[q];
400 volume += Jw;
401
402 // Evaluate Q1 function at the quadrature point via shape functions.
403 ScalarType u_q = ScalarType( 0 );
404 for ( int i = 0; i < fe::wedge::num_nodes_per_wedge; ++i )
405 u_q += local_u[wedge]( i ) * fe::wedge::shape< ScalarType >( i, quad_points[q] );
406
407 integral += u_q * Jw;
408 }
409 }
410
411 fv_grid( local_subdomain_id, hex_cell_x, hex_cell_y, hex_cell_r ) = integral / volume;
412 } );
413
414 Kokkos::fence();
416}
417
418} // namespace terra::fv::hex
Definition fe/wedge/operators/shell/mass.hpp:18
Parallel data structure organizing the thick spherical shell metadata for distributed (MPI parallel) ...
Definition spherical_shell.hpp:2518
Finite volume vector on distributed shell grid with one DoF per hex (merging 2 wedges) and ghost-laye...
Definition vector_fv.hpp:23
const grid::Grid4DDataScalar< ScalarType > & grid_data() const
Get const reference to grid data.
Definition vector_fv.hpp:145
const grid::Grid4DDataScalar< ScalarType > & grid_data() const
Get const reference to grid data.
Definition vector_q1.hpp:139
Definition iterative_solver_info.hpp:7
Preconditioned Conjugate Gradient (PCG) iterative solver for symmetric positive definite linear syste...
Definition pcg.hpp:30
Definition conversion.hpp:16
Ghost layer communication for scalar finite volume (FV) fields on the shell grid.
void send_recv(const grid::shell::DistributedDomain &domain, grid::Grid4DDataScalar< ScalarType > &grid, const CommunicationReduction reduction=CommunicationReduction::SUM)
Executes packing, sending, receiving, and unpacking operations for the shell.
Definition communication.hpp:750
void update_fv_ghost_layers(const grid::shell::DistributedDomain &domain, const grid::Grid4DDataScalar< ScalarType > &data, FVGhostLayerBuffers< ScalarType > &bufs)
Fills all ghost layers of a scalar FV field from neighbouring subdomains.
Definition fv_communication.hpp:316
constexpr void quad_felippa_3x2_quad_weights(T(&quad_weights)[quad_felippa_3x2_num_quad_points])
Definition wedge/quadrature/quadrature.hpp:93
constexpr int quad_felippa_3x2_num_quad_points
Definition wedge/quadrature/quadrature.hpp:66
constexpr void quad_felippa_3x2_quad_points(dense::Vec< T, 3 >(&quad_points)[quad_felippa_3x2_num_quad_points])
Definition wedge/quadrature/quadrature.hpp:70
constexpr int num_nodes_per_wedge_surface
Definition kernel_helpers.hpp:6
void atomically_add_local_wedge_scalar_coefficients(const grid::Grid4DDataScalar< T > &global_coefficients, const int local_subdomain_id, const int x_cell, const int y_cell, const int r_cell, const dense::Vec< T, 6 >(&local_coefficients)[2])
Performs an atomic add of the two local wedge coefficient vectors of a hex cell into the global coeff...
Definition kernel_helpers.hpp:407
void wedge_surface_physical_coords(dense::Vec< T, 3 >(&wedge_surf_phy_coords)[num_wedges_per_hex_cell][num_nodes_per_wedge_surface], const grid::Grid3DDataVec< T, 3 > &lateral_grid, const int local_subdomain_id, const int x_cell, const int y_cell)
Extracts the (unit sphere) surface vertex coords of the two wedges of a hex cell.
Definition kernel_helpers.hpp:26
constexpr int num_wedges_per_hex_cell
Definition kernel_helpers.hpp:5
void extract_local_wedge_scalar_coefficients(dense::Vec< T, 6 >(&local_coefficients)[2], const int local_subdomain_id, const int x_cell, const int y_cell, const int r_cell, const grid::Grid4DDataScalar< T > &global_coefficients)
Extracts the local vector coefficients for the two wedges of a hex cell from the global coefficient v...
Definition kernel_helpers.hpp:306
constexpr int num_nodes_per_wedge
Definition kernel_helpers.hpp:7
constexpr dense::Vec< T, 3 > forward_map(const dense::Vec< T, 3 > &p1_phy, const dense::Vec< T, 3 > &p2_phy, const dense::Vec< T, 3 > &p3_phy, const T r_1, const T r_2, const T xi, const T eta, const T zeta)
Definition integrands.hpp:596
constexpr T shape(const int node_idx, const T xi, const T eta, const T zeta)
(Tensor-product) Shape function.
Definition integrands.hpp:146
constexpr dense::Mat< T, 3, 3 > jac(const dense::Vec< T, 3 > &p1_phy, const dense::Vec< T, 3 > &p2_phy, const dense::Vec< T, 3 > &p3_phy, const T r_1, const T r_2, const T xi, const T eta, const T zeta)
Definition integrands.hpp:657
Definition conversion.hpp:13
void l2_project_analytical_to_fv(linalg::VectorFVScalar< ScalarType > &dst, const Functor &src, const grid::Grid3DDataVec< GridScalarType, 3 > &coords_shell, const grid::Grid2DDataScalar< GridScalarType > &coords_radii)
L2 projection of an analytical function into a finite volume function.
Definition conversion.hpp:43
void l2_project_fv_to_fe_lumped(linalg::VectorQ1Scalar< ScalarType > &dst, const linalg::VectorFVScalar< ScalarType > &src, const grid::shell::DistributedDomain &domain, const grid::Grid3DDataVec< GridScalarType, 3 > &coords_shell, const grid::Grid2DDataScalar< GridScalarType > &coords_radii, std::vector< linalg::VectorQ1Scalar< ScalarType > > &tmps)
Bound-preserving FV-to-FE transfer via a lumped mass matrix.
Definition conversion.hpp:243
void l2_project_fv_to_fe(linalg::VectorQ1Scalar< ScalarType > &dst, const linalg::VectorFVScalar< ScalarType > &src, const grid::shell::DistributedDomain &domain, const grid::Grid3DDataVec< GridScalarType, 3 > &coords_shell, const grid::Grid2DDataScalar< GridScalarType > &coords_radii, std::vector< linalg::VectorQ1Scalar< ScalarType > > &tmps)
L2 projection from a finite volume function into a Q1 (wedge) finite element function.
Definition conversion.hpp:131
void l2_project_fe_to_fv(linalg::VectorFVScalar< ScalarType > &dst, const linalg::VectorQ1Scalar< ScalarType > &src, const grid::shell::DistributedDomain &domain, const grid::Grid3DDataVec< GridScalarType, 3 > &coords_shell, const grid::Grid2DDataScalar< GridScalarType > &coords_radii)
L2 projection from a Q1 finite element function into a finite volume function.
Definition conversion.hpp:349
Kokkos::View< ScalarType ***[VecDim], Layout > Grid3DDataVec
Definition grid_types.hpp:42
Kokkos::View< ScalarType ****, Layout > Grid4DDataScalar
Definition grid_types.hpp:27
Kokkos::View< ScalarType **, Layout > Grid2DDataScalar
Definition grid_types.hpp:21
void solve(Solver &solver, Operator &A, SolutionVector &x, const RHSVector &b)
Solve a linear system using the given solver and operator. Calls the solver's solve_impl method.
Definition solver.hpp:51
void invert_entries(Vector &y)
Invert the entries of a vector. For each entry , computes .
Definition vector.hpp:127
void scale_in_place(Vector &y, const Vector &x)
Scale a vector in place with another vector. For each entry , computes .
Definition vector.hpp:137
void assign(Vector &y, const ScalarOf< Vector > &c0)
Assign a scalar value to a vector. Implements: .
Definition vector.hpp:97