Gyselalib++
 
Loading...
Searching...
No Matches
polarpoissonlikesolver.hpp
1// SPDX-License-Identifier: MIT
2#pragma once
3
4#include <ddc/ddc.hpp>
5
6#include <sll/gauss_legendre_integration.hpp>
7#include <sll/mapping/mapping_tools.hpp>
8#include <sll/mapping/metric_tensor.hpp>
9#include <sll/math_tools.hpp>
10#include <sll/matrix_batch_csr.hpp>
11#include <sll/polar_spline.hpp>
12#include <sll/polar_spline_evaluator.hpp>
13#include <sll/view.hpp>
14
15#include "ddc_alias_inline_functions.hpp"
16#include "ddc_aliases.hpp"
17
43template <
44 class GridR,
45 class GridTheta,
47 class SplineRThetaEvaluatorNullBound,
48 class IdxRangeFull = IdxRange<GridR, GridTheta>>
50{
51 // TODO: Add a batch loop to operator()
52 static_assert(
53 std::is_same_v<IdxRangeFull, IdxRange<GridR, GridTheta>>,
54 "PolarSplineFEMPoissonLikeSolver is not yet batched");
55
56public:
58 using R = typename GridR::continuous_dimension_type;
60 using Theta = typename GridTheta::continuous_dimension_type;
61
62public:
64 {
65 };
67 {
68 };
69 struct RCellDim
70 {
71 };
73 {
74 };
75
76
77public:
81 struct QDimRMesh : NonUniformGridBase<R>
82 {
83 };
87 struct QDimThetaMesh : NonUniformGridBase<Theta>
88 {
89 };
90
91private:
92 using CoordRTheta = Coord<R, Theta>;
93
98
99 using IdxRangeRTheta = IdxRange<GridR, GridTheta>;
100 using IdxRTheta = Idx<GridR, GridTheta>;
101
103 using IdxRangeBSPolar = IdxRange<PolarBSplinesRTheta>;
104 using IdxBSPolar = Idx<PolarBSplinesRTheta>;
105
106 using IdxRangeBSR = IdxRange<BSplinesR>;
107 using IdxRangeBSTheta = IdxRange<BSplinesTheta>;
108 using IdxRangeBSRTheta = IdxRange<BSplinesR, BSplinesTheta>;
109 using IdxBSRTheta = Idx<BSplinesR, BSplinesTheta>;
110
111 using IdxRangeBatchedBSRTheta
112 = ddc::detail::convert_type_seq_to_discrete_domain_t<ddc::type_seq_replace_t<
113 ddc::to_type_seq_t<IdxRangeFull>,
114 ddc::detail::TypeSeq<GridR, GridTheta>,
115 ddc::detail::TypeSeq<BSplinesR, BSplinesTheta>>>;
116
117 using IdxRangeBatch = ddc::remove_dims_of_t<IdxRangeFull, IdxRange<GridR>, IdxRange<GridTheta>>;
118
122 using IdxRangeQuadratureR = IdxRange<QDimRMesh>;
126 using IdxRangeQuadratureTheta = IdxRange<QDimThetaMesh>;
130 using IdxRangeQuadratureRTheta = IdxRange<QDimRMesh, QDimThetaMesh>;
134 using IdxQuadratureR = Idx<QDimRMesh>;
138 using IdxQuadratureTheta = Idx<QDimThetaMesh>;
142 using IdxQuadratureRTheta = Idx<QDimRMesh, QDimThetaMesh>;
146 using IdxStepQuadratureR = IdxStep<QDimRMesh>;
150 using IdxStepQuadratureTheta = IdxStep<QDimThetaMesh>;
151
152 using KnotsR = ddc::NonUniformBsplinesKnots<BSplinesR>;
153 using KnotsTheta = ddc::NonUniformBsplinesKnots<BSplinesTheta>;
154
155 using ConstSpline2D = DConstField<IdxRangeBatchedBSRTheta>;
157
158 using CoordFieldMemRTheta = FieldMem<CoordRTheta, IdxRangeRTheta>;
159 using CoordFieldRTheta = Field<CoordRTheta, IdxRangeRTheta>;
160 using DFieldRTheta = DField<IdxRangeRTheta>;
161
166 struct EvalDeriv1DType
167 {
168 double value;
169 double derivative;
170 };
175 struct EvalDeriv2DType
176 {
177 double value;
178 double radial_derivative;
179 double poloidal_derivative;
180 };
181
185 using IdxCell = Idx<RCellDim, ThetaCellDim>;
186
187private:
188 static constexpr int m_n_gauss_legendre_r = BSplinesR::degree() + 1;
189 static constexpr int m_n_gauss_legendre_theta = BSplinesTheta::degree() + 1;
190 // The number of cells (in the radial direction) in which both types of basis splines can be found
191 static constexpr int m_n_overlap_cells = PolarBSplinesRTheta::continuity + 1;
192
193 // Number of cells over which a radial B-splines has its support
194 // This is the case for b-splines which are not affected by the higher knot multiplicity at the boundary.
195 static constexpr IdxStep<RBasisSubset> m_n_non_zero_bases_r
196 = IdxStep<RBasisSubset>(BSplinesR::degree() + 1);
197
198 // Number of cells over which a poloidal B-splines has its support
199 static constexpr IdxStep<ThetaBasisSubset> m_n_non_zero_bases_theta
200 = IdxStep<ThetaBasisSubset>(BSplinesTheta::degree() + 1);
201
202 static constexpr IdxRange<RBasisSubset> m_non_zero_bases_r
203 = IdxRange<RBasisSubset>(Idx<RBasisSubset> {0}, m_n_non_zero_bases_r);
204 static constexpr IdxRange<ThetaBasisSubset> m_non_zero_bases_theta
205 = IdxRange<ThetaBasisSubset>(Idx<ThetaBasisSubset> {0}, m_n_non_zero_bases_theta);
206
207 const int m_nbasis_r;
208 const int m_nbasis_theta;
209
210 // Domains
211 IdxRangeBSPolar m_idxrange_fem_non_singular;
212 IdxRangeBSR m_idxrange_bsplines_r;
213 IdxRangeBSTheta m_idxrange_bsplines_theta;
214
215 IdxRangeQuadratureR m_idxrange_quadrature_r;
216 IdxRangeQuadratureTheta m_idxrange_quadrature_theta;
217 IdxRangeQuadratureRTheta m_idxrange_quadrature_singular;
218
219 // Gauss-Legendre points and weights
220 host_t<FieldMem<Coord<R>, IdxRangeQuadratureR>> m_points_r;
221 host_t<FieldMem<Coord<Theta>, IdxRangeQuadratureTheta>> m_points_theta;
222 host_t<FieldMem<double, IdxRangeQuadratureR>> m_weights_r;
223 host_t<FieldMem<double, IdxRangeQuadratureTheta>> m_weights_theta;
224
225 // Basis Spline values and derivatives at Gauss-Legendre points
226 host_t<FieldMem<EvalDeriv2DType, IdxRange<PolarBSplinesRTheta, QDimRMesh, QDimThetaMesh>>>
227 m_singular_basis_vals_and_derivs;
228 host_t<FieldMem<EvalDeriv1DType, IdxRange<RBasisSubset, QDimRMesh>>> r_basis_vals_and_derivs;
229 host_t<FieldMem<EvalDeriv1DType, IdxRange<ThetaBasisSubset, QDimThetaMesh>>>
230 m_theta_basis_vals_and_derivs;
231
232 host_t<FieldMem<double, IdxRangeQuadratureRTheta>> m_int_volume;
233
235 m_polar_spline_evaluator;
236 std::unique_ptr<MatrixBatchCsr<Kokkos::DefaultExecutionSpace, MatrixBatchCsrSolver::CG>>
237 m_gko_matrix;
238 mutable host_t<SplinePolar> m_phi_spline_coef;
239 Kokkos::View<double**, Kokkos::LayoutRight> m_x_init;
240
241 const int m_batch_idx {0}; // TODO: Remove when batching is supported
242
243public:
267 template <class Mapping>
269 host_t<ConstSpline2D> coeff_alpha,
270 host_t<ConstSpline2D> coeff_beta,
271 Mapping const& mapping,
272 SplineRThetaEvaluatorNullBound const& spline_evaluator)
273 : m_nbasis_r(ddc::discrete_space<BSplinesR>().nbasis() - m_n_overlap_cells - 1)
274 , m_nbasis_theta(ddc::discrete_space<BSplinesTheta>().nbasis())
275 , m_idxrange_fem_non_singular(
276 ddc::discrete_space<PolarBSplinesRTheta>().tensor_bspline_idx_range().remove_last(
277 IdxStep<PolarBSplinesRTheta> {m_nbasis_theta}))
278 , m_idxrange_bsplines_r(ddc::discrete_space<BSplinesR>().full_domain().remove_first(
279 IdxStep<BSplinesR> {m_n_overlap_cells}))
280 , m_idxrange_bsplines_theta(ddc::discrete_space<BSplinesTheta>().full_domain().take_first(
281 IdxStep<BSplinesTheta> {m_nbasis_theta}))
282 , m_idxrange_quadrature_r(
283 Idx<QDimRMesh>(0),
284 IdxStep<QDimRMesh>(
285 m_n_gauss_legendre_r * ddc::discrete_space<BSplinesR>().ncells()))
286 , m_idxrange_quadrature_theta(
287 Idx<QDimThetaMesh>(0),
288 IdxStep<QDimThetaMesh>(
289 m_n_gauss_legendre_theta * ddc::discrete_space<BSplinesTheta>().ncells()))
290 , m_idxrange_quadrature_singular(
291 m_idxrange_quadrature_r.take_first(
292 IdxStep<QDimRMesh> {m_n_overlap_cells * m_n_gauss_legendre_r}),
293 m_idxrange_quadrature_theta)
294 , m_points_r(m_idxrange_quadrature_r)
295 , m_points_theta(m_idxrange_quadrature_theta)
296 , m_weights_r(m_idxrange_quadrature_r)
297 , m_weights_theta(m_idxrange_quadrature_theta)
298 , m_singular_basis_vals_and_derivs(IdxRange<PolarBSplinesRTheta, QDimRMesh, QDimThetaMesh>(
299 PolarBSplinesRTheta::template singular_idx_range<PolarBSplinesRTheta>(),
300 ddc::select<QDimRMesh>(m_idxrange_quadrature_singular),
301 ddc::select<QDimThetaMesh>(m_idxrange_quadrature_singular)))
302 , r_basis_vals_and_derivs(
303 IdxRange<RBasisSubset, QDimRMesh>(m_non_zero_bases_r, m_idxrange_quadrature_r))
304 , m_theta_basis_vals_and_derivs(
305 IdxRange<
306 ThetaBasisSubset,
307 QDimThetaMesh>(m_non_zero_bases_theta, m_idxrange_quadrature_theta))
308 , m_int_volume(
309 IdxRangeQuadratureRTheta(m_idxrange_quadrature_r, m_idxrange_quadrature_theta))
310 , m_polar_spline_evaluator(ddc::NullExtrapolationRule())
311 , m_phi_spline_coef(
312 PolarBSplinesRTheta::template singular_idx_range<PolarBSplinesRTheta>(),
313 IdxRangeBSRTheta(
314 m_idxrange_bsplines_r,
315 ddc::discrete_space<BSplinesTheta>().full_domain()))
316 , m_x_init(
317 "x_init",
318 1,
319 ddc::discrete_space<PolarBSplinesRTheta>().nbasis()
320 - ddc::discrete_space<BSplinesTheta>().nbasis())
321 {
322 static_assert(has_2d_jacobian_v<Mapping, CoordRTheta>);
323 //initialize x_init
324 Kokkos::deep_copy(m_x_init, 0);
325 // Get break points
326 IdxRange<KnotsR> idxrange_r_edges = ddc::discrete_space<BSplinesR>().break_point_domain();
327 IdxRange<KnotsTheta> idxrange_theta_edges
328 = ddc::discrete_space<BSplinesTheta>().break_point_domain();
329 host_t<FieldMem<Coord<R>, IdxRange<KnotsR>>> breaks_r(idxrange_r_edges);
330 host_t<FieldMem<Coord<Theta>, IdxRange<KnotsTheta>>> breaks_theta(idxrange_theta_edges);
331
332 ddc::for_each(idxrange_r_edges, [&](Idx<KnotsR> i) { breaks_r(i) = ddc::coordinate(i); });
333 ddc::for_each(idxrange_theta_edges, [&](Idx<KnotsTheta> i) {
334 breaks_theta(i) = ddc::coordinate(i);
335 });
336
337 // Define quadrature points and weights
338 GaussLegendre<R> gl_coeffs_r(m_n_gauss_legendre_r);
339 GaussLegendre<Theta> gl_coeffs_theta(m_n_gauss_legendre_theta);
340 gl_coeffs_r.compute_points_and_weights_on_mesh(
341 get_field(m_points_r),
342 get_field(m_weights_r),
343 get_const_field(breaks_r));
344 gl_coeffs_theta.compute_points_and_weights_on_mesh(
345 get_field(m_points_theta),
346 get_field(m_weights_theta),
347 get_const_field(breaks_theta));
348
349 std::vector<double> vect_points_r(m_points_r.size());
350 for (IdxQuadratureR i : m_idxrange_quadrature_r) {
351 vect_points_r[i - m_idxrange_quadrature_r.front()] = m_points_r(i);
352 }
353 std::vector<double> vect_points_theta(m_points_theta.size());
354 for (IdxQuadratureTheta i : m_idxrange_quadrature_theta) {
355 vect_points_theta[i - m_idxrange_quadrature_theta.front()] = m_points_theta(i);
356 }
357
358 // Create quadrature index range
359 ddc::init_discrete_space<QDimRMesh>(vect_points_r);
360 ddc::init_discrete_space<QDimThetaMesh>(vect_points_theta);
361
362 // Find value and derivative of 1D bsplines in radial direction
363 ddc::for_each(m_idxrange_quadrature_r, [&](IdxQuadratureR const idx_r) {
364 std::array<double, 2 * m_n_non_zero_bases_r> data;
365 DSpan2D vals(data.data(), m_n_non_zero_bases_r, 2);
366 ddc::discrete_space<BSplinesR>()
367 .eval_basis_and_n_derivs(vals, ddc::coordinate(idx_r), 1);
368 for (auto ib : m_non_zero_bases_r) {
369 const int ib_idx = ib - m_non_zero_bases_r.front();
370 r_basis_vals_and_derivs(ib, idx_r).value = vals(ib_idx, 0);
371 r_basis_vals_and_derivs(ib, idx_r).derivative = vals(ib_idx, 1);
372 }
373 });
374
375 // Find value and derivative of 1D bsplines in poloidal direction
376 ddc::for_each(m_idxrange_quadrature_theta, [&](IdxQuadratureTheta const idx_theta) {
377 std::array<double, 2 * m_n_non_zero_bases_theta> data;
378 DSpan2D vals(data.data(), m_n_non_zero_bases_theta, 2);
379 ddc::discrete_space<BSplinesTheta>()
380 .eval_basis_and_n_derivs(vals, ddc::coordinate(idx_theta), 1);
381 for (auto ib : m_non_zero_bases_theta) {
382 const int ib_idx = ib - m_non_zero_bases_theta.front();
383 m_theta_basis_vals_and_derivs(ib, idx_theta).value = vals(ib_idx, 0);
384 m_theta_basis_vals_and_derivs(ib, idx_theta).derivative = vals(ib_idx, 1);
385 }
386 });
387
388 IdxRangeBSPolar idxrange_singular
389 = PolarBSplinesRTheta::template singular_idx_range<PolarBSplinesRTheta>();
390
391 // Find value and derivative of 2D bsplines covering the singular point
392 ddc::for_each(m_idxrange_quadrature_singular, [&](IdxQuadratureRTheta const irp) {
393 std::array<double, PolarBSplinesRTheta::n_singular_basis()> singular_data;
394 std::array<double, m_n_non_zero_bases_r * m_n_non_zero_bases_theta> data;
395 // Values of the polar basis splines around the singular point
396 // at a given coordinate
397 DSpan1D singular_vals(singular_data.data(), PolarBSplinesRTheta::n_singular_basis());
398 // Values of the polar basis splines, that do not cover the singular point,
399 // at a given coordinate
400 DSpan2D vals(data.data(), m_n_non_zero_bases_r, m_n_non_zero_bases_theta);
401 IdxQuadratureR idx_r = ddc::select<QDimRMesh>(irp);
402 IdxQuadratureTheta idx_theta = ddc::select<QDimThetaMesh>(irp);
403
404 const CoordRTheta coord(ddc::coordinate(irp));
405
406 // Calculate the value
407 ddc::discrete_space<PolarBSplinesRTheta>().eval_basis(singular_vals, vals, coord);
408 for (IdxBSPolar ib : idxrange_singular) {
409 m_singular_basis_vals_and_derivs(ib, idx_r, idx_theta).value
410 = singular_vals[ib - idxrange_singular.front()];
411 }
412
413 // Calculate the radial derivative
414 ddc::discrete_space<PolarBSplinesRTheta>().eval_deriv_r(singular_vals, vals, coord);
415 for (IdxBSPolar ib : idxrange_singular) {
416 m_singular_basis_vals_and_derivs(ib, idx_r, idx_theta).radial_derivative
417 = singular_vals[ib - idxrange_singular.front()];
418 }
419
420 // Calculate the poloidal derivative
421 ddc::discrete_space<PolarBSplinesRTheta>().eval_deriv_theta(singular_vals, vals, coord);
422 for (IdxBSPolar ib : idxrange_singular) {
423 m_singular_basis_vals_and_derivs(ib, idx_r, idx_theta).poloidal_derivative
424 = singular_vals[ib - idxrange_singular.front()];
425 }
426 });
427
428 // Find the integral volume associated with each point used in the quadrature scheme
429 IdxRangeQuadratureRTheta
430 all_quad_points(m_idxrange_quadrature_r, m_idxrange_quadrature_theta);
431 ddc::for_each(all_quad_points, [&](IdxQuadratureRTheta const irp) {
432 IdxQuadratureR const idx_r = ddc::select<QDimRMesh>(irp);
433 IdxQuadratureTheta const idx_theta = ddc::select<QDimThetaMesh>(irp);
434 CoordRTheta coord(ddc::coordinate(irp));
435 m_int_volume(idx_r, idx_theta) = abs(mapping.jacobian(coord)) * m_weights_r(idx_r)
436 * m_weights_theta(idx_theta);
437 });
438
439 // Number of elements in the matrix that correspond to the splines
440 // that cover the singular point
441 constexpr int n_elements_singular
443 // Number of non-zero elements in the matrix corresponding to the inner product of
444 // polar splines at the singular point and the other splines
445 const int n_elements_overlap = 2
447 * BSplinesR::degree() * m_nbasis_theta);
448 const int n_stencil_theta
449 = m_nbasis_theta * min(int(1 + 2 * BSplinesTheta::degree()), m_nbasis_theta);
450 const int n_stencil_r = m_nbasis_r * (1 + 2 * BSplinesR::degree())
451 - (1 + BSplinesR::degree()) * BSplinesR::degree();
452 // Number of non-zero elements in the matrix corresponding to the inner product of
453 // non-central splines. These have a tensor product structure
454 const int n_elements_stencil = n_stencil_r * n_stencil_theta;
455
456 const int batch_size = 1;
457 // Matrix size is equal to the number Polar bspline
458 const int matrix_size
459 = ddc::discrete_space<PolarBSplinesRTheta>().nbasis() - m_nbasis_theta;
460 const int n_matrix_elements = n_elements_singular + n_elements_overlap + n_elements_stencil;
461
462 //CSR data storage
463 Kokkos::View<double**, Kokkos::LayoutRight, Kokkos::HostSpace>
464 values_csr_host("values_csr", batch_size, n_matrix_elements);
465 Kokkos::View<int*, Kokkos::LayoutRight, Kokkos::HostSpace>
466 col_idx_csr_host("idx_csr", n_matrix_elements);
467 Kokkos::View<int*, Kokkos::LayoutRight, Kokkos::DefaultExecutionSpace>
468 nnz_per_row_csr("nnz_per_row_csr", matrix_size + 1);
469 Kokkos::View<int*, Kokkos::LayoutRight, Kokkos::HostSpace>
470 nnz_per_row_csr_host("nnz_per_row_csr", matrix_size + 1);
471
472 init_nnz_per_line(nnz_per_row_csr);
473 Kokkos::deep_copy(nnz_per_row_csr_host, nnz_per_row_csr);
474
475 Kokkos::Profiling::pushRegion("PolarPoissonFillFemMatrix");
476 // Calculate the matrix elements corresponding to the bsplines which cover the singular point
477 ddc::for_each(idxrange_singular, [&](IdxBSPolar const idx_test) {
478 ddc::for_each(idxrange_singular, [&](IdxBSPolar const idx_trial) {
479 // Calculate the weak integral
480 double const element = ddc::transform_reduce(
481 m_idxrange_quadrature_singular,
482 0.0,
483 ddc::reducer::sum<double>(),
484 [&](IdxQuadratureRTheta const idx_quad) {
485 IdxQuadratureR const idx_r = ddc::select<QDimRMesh>(idx_quad);
486 IdxQuadratureTheta const idx_theta
487 = ddc::select<QDimThetaMesh>(idx_quad);
488 return weak_integral_element(
489 idx_r,
490 idx_theta,
491 m_singular_basis_vals_and_derivs(idx_test, idx_r, idx_theta),
492 m_singular_basis_vals_and_derivs(idx_trial, idx_r, idx_theta),
493 coeff_alpha,
494 coeff_beta,
495 spline_evaluator,
496 mapping);
497 });
498 const int row_idx = idx_test - idxrange_singular.front();
499 const int col_idx = idx_trial - idxrange_singular.front();
500 const int csr_idx_singular_area = nnz_per_row_csr_host(row_idx + 1);
501 //Fill the C matrix corresponding to the splines on the singular point
502 col_idx_csr_host(csr_idx_singular_area) = col_idx;
503 values_csr_host(m_batch_idx, csr_idx_singular_area) = element;
504 nnz_per_row_csr_host(row_idx + 1)++;
505 });
506 });
507
508 // Create index ranges associated with the 2D splines
509 IdxRangeBSR central_radial_bspline_idx_range(
510 m_idxrange_bsplines_r.take_first(IdxStep<BSplinesR> {BSplinesR::degree()}));
511
512 IdxRangeBSRTheta idxrange_non_singular_near_centre(
513 central_radial_bspline_idx_range,
514 m_idxrange_bsplines_theta);
515
516 // Calculate the matrix elements where bspline products overlap the bsplines which cover the singular point
517 ddc::for_each(idxrange_singular, [&](IdxBSPolar const idx_test) {
518 ddc::for_each(idxrange_non_singular_near_centre, [&](IdxBSRTheta const idx_trial) {
519 const IdxBSPolar idx_trial_polar(
520 PolarBSplinesRTheta::template get_polar_index<PolarBSplinesRTheta>(
521 idx_trial));
522 const Idx<BSplinesR> idx_trial_r(ddc::select<BSplinesR>(idx_trial));
523 const Idx<BSplinesTheta> idx_trial_theta(ddc::select<BSplinesTheta>(idx_trial));
524
525 // Find the index range covering the cells where both the test and trial functions are non-zero
526 const Idx<RCellDim> first_overlap_element_r(
527 idx_trial_r.uid() < BSplinesR::degree()
528 ? 0
529 : idx_trial_r.uid() - BSplinesR::degree());
530 const Idx<ThetaCellDim> first_overlap_element_theta(
531 theta_mod(idx_trial_theta.uid() - BSplinesTheta::degree()));
532
533 const IdxStep<RCellDim> n_overlap_r(
534 m_n_overlap_cells - first_overlap_element_r.uid());
535 const IdxStep<ThetaCellDim> n_overlap_theta(BSplinesTheta::degree() + 1);
536
537 const IdxRange<RCellDim> r_cells(first_overlap_element_r, n_overlap_r);
538 const IdxRange<ThetaCellDim>
539 theta_cells(first_overlap_element_theta, n_overlap_theta);
540 const IdxRange<RCellDim, ThetaCellDim> non_zero_cells(r_cells, theta_cells);
541
542 if (n_overlap_r > 0) {
543 double element = 0.0;
544
545 ddc::for_each(non_zero_cells, [&](IdxCell const cell_idx) {
546 const int cell_idx_r(ddc::select<RCellDim>(cell_idx).uid());
547 const int cell_idx_theta(
548 theta_mod(ddc::select<ThetaCellDim>(cell_idx).uid()));
549
550 const IdxRangeQuadratureRTheta cell_quad_points(
551 get_quadrature_points_in_cell(cell_idx_r, cell_idx_theta));
552 // Find the column where the non-zero data is stored
553 Idx<RBasisSubset> ib_trial_r(idx_trial_r.uid() - cell_idx_r);
554 Idx<ThetaBasisSubset> ib_trial_theta(
555 theta_mod(idx_trial_theta.uid() - cell_idx_theta));
556 // Calculate the weak integral
557 element += ddc::transform_reduce(
558 cell_quad_points,
559 0.0,
560 ddc::reducer::sum<double>(),
561 [&](IdxQuadratureRTheta const idx_quad) {
562 IdxQuadratureR const idx_r = ddc::select<QDimRMesh>(idx_quad);
563 IdxQuadratureTheta const idx_theta
564 = ddc::select<QDimThetaMesh>(idx_quad);
565 return weak_integral_element<Mapping>(
566 idx_r,
567 idx_theta,
568 m_singular_basis_vals_and_derivs(
569 idx_test,
570 idx_r,
571 idx_theta),
572 r_basis_vals_and_derivs(ib_trial_r, idx_r),
573 m_theta_basis_vals_and_derivs(
574 ib_trial_theta,
575 idx_theta),
576 coeff_alpha,
577 coeff_beta,
578 spline_evaluator,
579 mapping);
580 });
581 });
582
583 int const row_idx = idx_test - idxrange_singular.front();
584 int const col_idx = idx_trial_polar - idxrange_singular.front();
585 //a_ij
586 col_idx_csr_host(nnz_per_row_csr_host(row_idx + 1)) = col_idx;
587 values_csr_host(m_batch_idx, nnz_per_row_csr_host(row_idx + 1)) = element;
588 nnz_per_row_csr_host(row_idx + 1)++;
589 //a_ji
590 col_idx_csr_host(nnz_per_row_csr_host(col_idx + 1)) = row_idx;
591 values_csr_host(m_batch_idx, nnz_per_row_csr_host(col_idx + 1)) = element;
592 nnz_per_row_csr_host(col_idx + 1)++;
593 }
594 });
595 });
596
597 // Calculate the matrix elements following a stencil
598 ddc::for_each(m_idxrange_fem_non_singular, [&](IdxBSPolar const idx_test_polar) {
599 const IdxBSRTheta idx_test(PolarBSplinesRTheta::get_2d_index(idx_test_polar));
600 const std::size_t idx_test_r(ddc::select<BSplinesR>(idx_test).uid());
601 const std::size_t idx_test_theta(ddc::select<BSplinesTheta>(idx_test).uid());
602
603 // Calculate the index of the elements that are already filled
604 IdxRangeBSTheta remaining_theta(
605 Idx<BSplinesTheta> {idx_test_theta},
606 IdxStep<BSplinesTheta> {BSplinesTheta::degree() + 1});
607 ddc::for_each(remaining_theta, [&](Idx<BSplinesTheta> const idx_trial_theta) {
608 IdxBSRTheta idx_trial(Idx<BSplinesR>(idx_test_r), idx_trial_theta);
609 IdxBSPolar idx_trial_polar(
610 PolarBSplinesRTheta::template get_polar_index<PolarBSplinesRTheta>(
611 IdxBSRTheta(idx_test_r, theta_mod(idx_trial_theta.uid()))));
612 double element = get_matrix_stencil_element(
613 idx_test,
614 idx_trial,
615 coeff_alpha,
616 coeff_beta,
617 spline_evaluator,
618 mapping);
619 int const int_polar_idx_test = idx_test_polar - idxrange_singular.front();
620 if (idx_test_polar == idx_trial_polar) {
621 const int idx = nnz_per_row_csr_host(int_polar_idx_test + 1);
622 col_idx_csr_host(idx) = int_polar_idx_test;
623 values_csr_host(m_batch_idx, idx) = element;
624 nnz_per_row_csr_host(int_polar_idx_test + 1)++;
625 } else {
626 int const int_polar_idx_trial = idx_trial_polar - idxrange_singular.front();
627
628 const int aij_idx = nnz_per_row_csr_host(int_polar_idx_test + 1);
629 col_idx_csr_host(aij_idx) = int_polar_idx_trial;
630 values_csr_host(m_batch_idx, aij_idx) = element;
631 nnz_per_row_csr_host(int_polar_idx_test + 1)++;
632
633 const int aji_idx = nnz_per_row_csr_host(int_polar_idx_trial + 1);
634 col_idx_csr_host(aji_idx) = int_polar_idx_test;
635 values_csr_host(m_batch_idx, aji_idx) = element;
636 nnz_per_row_csr_host(int_polar_idx_trial + 1)++;
637 }
638 });
639 IdxRangeBSR remaining_r(
640 ddc::select<BSplinesR>(idx_test) + 1,
641 IdxStep<BSplinesR> {
642 min(BSplinesR::degree(),
643 ddc::discrete_space<BSplinesR>().nbasis() - 2 - idx_test_r)});
644 IdxRangeBSTheta relevant_theta(
645 Idx<BSplinesTheta> {
646 idx_test_theta + ddc::discrete_space<BSplinesTheta>().nbasis()
647 - BSplinesTheta::degree()},
648 IdxStep<BSplinesTheta> {2 * BSplinesTheta::degree() + 1});
649
650 IdxRangeBSRTheta trial_idx_range(remaining_r, relevant_theta);
651
652 ddc::for_each(trial_idx_range, [&](IdxBSRTheta const idx_trial) {
653 const int idx_trial_r(ddc::select<BSplinesR>(idx_trial).uid());
654 const int idx_trial_theta(ddc::select<BSplinesTheta>(idx_trial).uid());
655 IdxBSPolar idx_trial_polar(
656 PolarBSplinesRTheta::template get_polar_index<PolarBSplinesRTheta>(
657 IdxBSRTheta(idx_trial_r, theta_mod(idx_trial_theta))));
658 double element = get_matrix_stencil_element(
659 idx_test,
660 idx_trial,
661 coeff_alpha,
662 coeff_beta,
663 spline_evaluator,
664 mapping);
665 int const int_polar_idx_test = idx_test_polar - idxrange_singular.front();
666 if (idx_test_polar == idx_trial_polar) {
667 const int idx = nnz_per_row_csr_host(int_polar_idx_test + 1);
668 col_idx_csr_host(idx) = int_polar_idx_test;
669 values_csr_host(m_batch_idx, idx) = element;
670 nnz_per_row_csr_host(int_polar_idx_test + 1)++;
671 } else {
672 int const int_polar_idx_trial = idx_trial_polar - idxrange_singular.front();
673 const int aij_idx = nnz_per_row_csr_host(int_polar_idx_test + 1);
674 col_idx_csr_host(aij_idx) = int_polar_idx_trial;
675 values_csr_host(m_batch_idx, aij_idx) = element;
676 nnz_per_row_csr_host(int_polar_idx_test + 1)++;
677
678 const int aji_idx = nnz_per_row_csr_host(int_polar_idx_trial + 1);
679 col_idx_csr_host(aji_idx) = int_polar_idx_test;
680 values_csr_host(m_batch_idx, aji_idx) = element;
681 nnz_per_row_csr_host(int_polar_idx_trial + 1)++;
682 }
683 });
684 });
685 assert(nnz_per_row_csr_host(matrix_size) == n_matrix_elements);
686 m_gko_matrix = std::make_unique<MatrixBatchCsr<
687 Kokkos::DefaultExecutionSpace,
688 MatrixBatchCsrSolver::CG>>(1, matrix_size, n_matrix_elements);
689
690 auto [values, col_idx, nnz_per_row] = m_gko_matrix->get_batch_csr();
691 Kokkos::deep_copy(values, values_csr_host);
692 Kokkos::deep_copy(col_idx, col_idx_csr_host);
693 Kokkos::deep_copy(nnz_per_row, nnz_per_row_csr_host);
694 m_gko_matrix->setup_solver();
695 Kokkos::Profiling::popRegion();
696 }
697
711 template <class RHSFunction>
712 void operator()(RHSFunction const& rhs, host_t<SplinePolar>& spline) const
713 {
714 Kokkos::Profiling::pushRegion("PolarPoissonRHS");
715
716 static_assert(
717 std::is_invocable_r_v<double, RHSFunction, CoordRTheta>,
718 "RHSFunction must have an operator() which takes a coordinate and returns a "
719 "double");
720 const int b_size = ddc::discrete_space<PolarBSplinesRTheta>().nbasis()
721 - ddc::discrete_space<BSplinesTheta>().nbasis();
722 const int batch_size = 1;
723 // Create b for rhs
724 Kokkos::View<double**, Kokkos::LayoutRight, Kokkos::HostSpace>
725 b_host("b_host", batch_size, b_size);
726 //Create an initial guess
727 Kokkos::View<double**, Kokkos::LayoutRight, Kokkos::HostSpace>
728 x_init_host("x_init_host", batch_size, b_size);
729 // Fill b
730 ddc::for_each(
731 PolarBSplinesRTheta::template singular_idx_range<PolarBSplinesRTheta>(),
732 [&](IdxBSPolar const idx) {
733 const int bspl_idx = idx
734 - PolarBSplinesRTheta::template singular_idx_range<
736 .front();
737 b_host(0, bspl_idx) = ddc::transform_reduce(
738 m_idxrange_quadrature_singular,
739 0.0,
740 ddc::reducer::sum<double>(),
741 [&](IdxQuadratureRTheta const idx_quad) {
742 IdxQuadratureR const idx_r = ddc::select<QDimRMesh>(idx_quad);
743 IdxQuadratureTheta const idx_theta
744 = ddc::select<QDimThetaMesh>(idx_quad);
745 CoordRTheta coord(ddc::coordinate(idx_quad));
746 return rhs(coord)
747 * m_singular_basis_vals_and_derivs(idx, idx_r, idx_theta)
748 .value
749 * m_int_volume(idx_r, idx_theta);
750 });
751 });
752 const std::size_t ncells_r = ddc::discrete_space<BSplinesR>().ncells();
753 ddc::for_each(m_idxrange_fem_non_singular, [&](IdxBSPolar const idx) {
754 const IdxBSRTheta idx_2d(PolarBSplinesRTheta::get_2d_index(idx));
755 const std::size_t idx_r(ddc::select<BSplinesR>(idx_2d).uid());
756 const std::size_t idx_theta(ddc::select<BSplinesTheta>(idx_2d).uid());
757
758 // Find the cells on which the bspline is non-zero
759 int first_cell_r(idx_r - BSplinesR::degree());
760 int first_cell_theta(idx_theta - BSplinesTheta::degree());
761 std::size_t last_cell_r(idx_r + 1);
762 if (first_cell_r < 0)
763 first_cell_r = 0;
764 if (last_cell_r > ncells_r)
765 last_cell_r = ncells_r;
766 IdxStep<RCellDim> const r_length(last_cell_r - first_cell_r);
767 IdxStep<ThetaCellDim> const theta_length(BSplinesTheta::degree() + 1);
768
769
770 Idx<RCellDim> const start_r(first_cell_r);
771 Idx<ThetaCellDim> const start_theta(theta_mod(first_cell_theta));
772 const IdxRange<RCellDim> r_cells(start_r, r_length);
773 const IdxRange<ThetaCellDim> theta_cells(start_theta, theta_length);
774 const IdxRange<RCellDim, ThetaCellDim> non_zero_cells(r_cells, theta_cells);
775 assert(r_length * theta_length > 0);
776 double element = 0.0;
777 ddc::for_each(non_zero_cells, [&](IdxCell const cell_idx) {
778 const int cell_idx_r(ddc::select<RCellDim>(cell_idx).uid());
779 const int cell_idx_theta(theta_mod(ddc::select<ThetaCellDim>(cell_idx).uid()));
780
781 const IdxRangeQuadratureRTheta cell_quad_points(
782 get_quadrature_points_in_cell(cell_idx_r, cell_idx_theta));
783
784 // Find the column where the non-zero data is stored
785 Idx<RBasisSubset> ib_r(idx_r - cell_idx_r);
786 Idx<ThetaBasisSubset> ib_theta(theta_mod(idx_theta - cell_idx_theta));
787
788 // Calculate the weak integral
789 element += ddc::transform_reduce(
790 cell_quad_points,
791 0.0,
792 ddc::reducer::sum<double>(),
793 [&](IdxQuadratureRTheta const idx_quad) {
794 IdxQuadratureR const idx_r = ddc::select<QDimRMesh>(idx_quad);
795 IdxQuadratureTheta const idx_theta
796 = ddc::select<QDimThetaMesh>(idx_quad);
797 CoordRTheta coord(ddc::coordinate(idx_quad));
798 double rb = r_basis_vals_and_derivs(ib_r, idx_r).value;
799 double pb = m_theta_basis_vals_and_derivs(ib_theta, idx_theta).value;
800 return rhs(coord) * rb * pb * m_int_volume(idx_r, idx_theta);
801 });
802 });
803 const std::size_t singular_index
804 = idx - ddc::discrete_space<PolarBSplinesRTheta>().full_domain().front();
805 b_host(0, singular_index) = element;
806 });
807
808 Kokkos::View<double**, Kokkos::LayoutRight> b("b", batch_size, b_size);
809 Kokkos::deep_copy(b, b_host);
810 Kokkos::Profiling::popRegion();
811
812 Kokkos::deep_copy(m_x_init, x_init_host);
813 // Solve the matrix equation
814 Kokkos::Profiling::pushRegion("PolarPoissonSolve");
815 m_gko_matrix->solve(m_x_init, b);
816 Kokkos::deep_copy(x_init_host, m_x_init);
817 //-----------------
818 IdxRangeBSRTheta dirichlet_boundary_idx_range(
819 m_idxrange_bsplines_r.take_last(IdxStep<BSplinesR> {1}),
820 m_idxrange_bsplines_theta);
821 IdxRangeBSTheta idxrange_polar(ddc::discrete_space<BSplinesTheta>().full_domain());
822
823
824 // Fill the spline
825 ddc::for_each(
826 PolarBSplinesRTheta::template singular_idx_range<PolarBSplinesRTheta>(),
827 [&](IdxBSPolar const idx) {
828 const int bspl_idx = idx
829 - PolarBSplinesRTheta::template singular_idx_range<
831 .front();
832 spline.singular_spline_coef(idx) = x_init_host(0, bspl_idx);
833 });
834 ddc::for_each(m_idxrange_fem_non_singular, [&](IdxBSPolar const idx) {
835 const IdxBSRTheta idx_2d(PolarBSplinesRTheta::get_2d_index(idx));
836 spline.spline_coef(idx_2d) = x_init_host(0, idx.uid());
837 });
838 ddc::for_each(dirichlet_boundary_idx_range, [&](IdxBSRTheta const idx) {
839 spline.spline_coef(idx) = 0.0;
840 });
841
842 // Copy the periodic elements
843 IdxRangeBSRTheta copy_idx_range(
844 m_idxrange_bsplines_r,
845 idxrange_polar.remove_first(
846 IdxStep<BSplinesTheta>(ddc::discrete_space<BSplinesTheta>().nbasis())));
847 ddc::for_each(copy_idx_range, [&](IdxBSRTheta const idx_2d) {
848 spline.spline_coef(ddc::select<BSplinesR>(idx_2d), ddc::select<BSplinesTheta>(idx_2d))
849 = spline.spline_coef(
850 ddc::select<BSplinesR>(idx_2d),
851 ddc::select<BSplinesTheta>(idx_2d)
852 - ddc::discrete_space<BSplinesTheta>().nbasis());
853 });
854 Kokkos::Profiling::popRegion();
855 }
856
857
871 template <class RHSFunction>
872 void operator()(RHSFunction const& rhs, host_t<DFieldRTheta> phi) const
873 {
874 static_assert(
875 std::is_invocable_r_v<double, RHSFunction, CoordRTheta>,
876 "RHSFunction must have an operator() which takes a coordinate and returns a "
877 "double");
878
879
880 (*this)(rhs, m_phi_spline_coef);
881 host_t<CoordFieldMemRTheta> coords_eval_alloc(get_idx_range(phi));
882 host_t<CoordFieldRTheta> coords_eval(get_field(coords_eval_alloc));
883 ddc::for_each(get_idx_range(phi), [&](IdxRTheta idx) {
884 coords_eval(idx) = ddc::coordinate(idx);
885 });
886 m_polar_spline_evaluator(phi, get_const_field(coords_eval), m_phi_spline_coef);
887 }
888
889private:
890 static KOKKOS_FUNCTION IdxRangeQuadratureRTheta
891 get_quadrature_points_in_cell(int cell_idx_r, int cell_idx_theta)
892 {
893 const IdxQuadratureR first_quad_point_r(cell_idx_r * m_n_gauss_legendre_r);
894 const IdxQuadratureTheta first_quad_point_theta(cell_idx_theta * m_n_gauss_legendre_theta);
895 constexpr IdxStepQuadratureR n_GL_r(m_n_gauss_legendre_r);
896 constexpr IdxStepQuadratureTheta n_GL_theta(m_n_gauss_legendre_theta);
897 const IdxRangeQuadratureR quad_points_r(first_quad_point_r, n_GL_r);
898 const IdxRangeQuadratureTheta quad_points_theta(first_quad_point_theta, n_GL_theta);
899 return IdxRangeQuadratureRTheta(quad_points_r, quad_points_theta);
900 }
901
902 template <class Mapping>
903 double weak_integral_element(
904 IdxQuadratureR idx_r,
905 IdxQuadratureTheta idx_theta,
906 EvalDeriv2DType const& test_bspline_val_and_deriv,
907 EvalDeriv2DType const& trial_bspline_val_and_deriv,
908 host_t<ConstSpline2D> coeff_alpha,
909 host_t<ConstSpline2D> coeff_beta,
910 SplineRThetaEvaluatorNullBound const& evaluator,
911 Mapping const& mapping)
912 {
913 return templated_weak_integral_element(
914 idx_r,
915 idx_theta,
916 test_bspline_val_and_deriv,
917 trial_bspline_val_and_deriv,
918 test_bspline_val_and_deriv,
919 trial_bspline_val_and_deriv,
920 coeff_alpha,
921 coeff_beta,
922 evaluator,
923 mapping);
924 }
925
926 template <class Mapping>
927 double weak_integral_element(
928 IdxQuadratureR idx_r,
929 IdxQuadratureTheta idx_theta,
930 EvalDeriv2DType const& test_bspline_val_and_deriv,
931 EvalDeriv1DType const& trial_bspline_val_and_deriv_r,
932 EvalDeriv1DType const& trial_bspline_val_and_deriv_theta,
933 host_t<ConstSpline2D> coeff_alpha,
934 host_t<ConstSpline2D> coeff_beta,
935 SplineRThetaEvaluatorNullBound const& evaluator,
936 Mapping const& mapping)
937 {
938 return templated_weak_integral_element(
939 idx_r,
940 idx_theta,
941 test_bspline_val_and_deriv,
942 trial_bspline_val_and_deriv_r,
943 test_bspline_val_and_deriv,
944 trial_bspline_val_and_deriv_theta,
945 coeff_alpha,
946 coeff_beta,
947 evaluator,
948 mapping);
949 }
950
951 template <class Mapping>
952 double weak_integral_element(
953 IdxQuadratureR idx_r,
954 IdxQuadratureTheta idx_theta,
955 EvalDeriv1DType const& test_bspline_val_and_deriv_r,
956 EvalDeriv2DType const& trial_bspline_val_and_deriv,
957 EvalDeriv1DType const& test_bspline_val_and_deriv_theta,
958 host_t<ConstSpline2D> coeff_alpha,
959 host_t<ConstSpline2D> coeff_beta,
960 SplineRThetaEvaluatorNullBound const& evaluator,
961 Mapping const& mapping)
962 {
963 return templated_weak_integral_element(
964 idx_r,
965 idx_theta,
966 test_bspline_val_and_deriv_r,
967 trial_bspline_val_and_deriv,
968 test_bspline_val_and_deriv_theta,
969 trial_bspline_val_and_deriv,
970 coeff_alpha,
971 coeff_beta,
972 evaluator,
973 mapping);
974 }
975
976 template <class Mapping>
977 double weak_integral_element(
978 IdxQuadratureR idx_r,
979 IdxQuadratureTheta idx_theta,
980 EvalDeriv1DType const& test_bspline_val_and_deriv_r,
981 EvalDeriv1DType const& trial_bspline_val_and_deriv_r,
982 EvalDeriv1DType const& test_bspline_val_and_deriv_theta,
983 EvalDeriv1DType const& trial_bspline_val_and_deriv_theta,
984 host_t<ConstSpline2D> coeff_alpha,
985 host_t<ConstSpline2D> coeff_beta,
986 SplineRThetaEvaluatorNullBound const& evaluator,
987 Mapping const& mapping)
988 {
989 return templated_weak_integral_element(
990 idx_r,
991 idx_theta,
992 test_bspline_val_and_deriv_r,
993 trial_bspline_val_and_deriv_r,
994 test_bspline_val_and_deriv_theta,
995 trial_bspline_val_and_deriv_theta,
996 coeff_alpha,
997 coeff_beta,
998 evaluator,
999 mapping);
1000 }
1001
1002 inline void get_value_and_gradient(
1003 double& value,
1004 std::array<double, 2>& gradient,
1005 EvalDeriv1DType const& r_basis,
1006 EvalDeriv1DType const& theta_basis) const
1007 {
1008 value = r_basis.value * theta_basis.value;
1009 gradient = {r_basis.derivative * theta_basis.value, r_basis.value * theta_basis.derivative};
1010 }
1011
1012 inline void get_value_and_gradient(
1013 double& value,
1014 std::array<double, 2>& gradient,
1015 EvalDeriv2DType const& basis,
1016 EvalDeriv2DType const&) const // Last argument is duplicate
1017 {
1018 value = basis.value;
1019 gradient = {basis.radial_derivative, basis.poloidal_derivative};
1020 }
1021
1031 template <class Mapping, class TestValDerivType, class TrialValDerivType>
1032 double templated_weak_integral_element(
1033 IdxQuadratureR idx_r,
1034 IdxQuadratureTheta idx_theta,
1035 TestValDerivType const& test_bspline_val_and_deriv,
1036 TrialValDerivType const& trial_bspline_val_and_deriv,
1037 TestValDerivType const& test_bspline_val_and_deriv_theta,
1038 TrialValDerivType const& trial_bspline_val_and_deriv_theta,
1039 host_t<ConstSpline2D> coeff_alpha,
1040 host_t<ConstSpline2D> coeff_beta,
1041 SplineRThetaEvaluatorNullBound const& spline_evaluator,
1042 Mapping const& mapping)
1043 {
1044 static_assert(
1045 std::is_same_v<
1046 TestValDerivType,
1047 EvalDeriv1DType> || std::is_same_v<TestValDerivType, EvalDeriv2DType>);
1048 static_assert(
1049 std::is_same_v<
1050 TrialValDerivType,
1051 EvalDeriv1DType> || std::is_same_v<TrialValDerivType, EvalDeriv2DType>);
1052
1053 // Calculate coefficients at quadrature point
1054 CoordRTheta coord(ddc::coordinate(idx_r), ddc::coordinate(idx_theta));
1055 const double alpha = spline_evaluator(coord, coeff_alpha);
1056 const double beta = spline_evaluator(coord, coeff_beta);
1057
1058 // Define the value and gradient of the test and trial basis functions
1059 double basis_val_test_space;
1060 double basis_val_trial_space;
1061 std::array<double, 2> basis_gradient_test_space;
1062 std::array<double, 2> basis_gradient_trial_space;
1063 get_value_and_gradient(
1064 basis_val_test_space,
1065 basis_gradient_test_space,
1066 test_bspline_val_and_deriv,
1067 test_bspline_val_and_deriv_theta);
1068 get_value_and_gradient(
1069 basis_val_trial_space,
1070 basis_gradient_trial_space,
1071 trial_bspline_val_and_deriv,
1072 trial_bspline_val_and_deriv_theta);
1073
1074 MetricTensor<Mapping, CoordRTheta> metric_tensor(mapping);
1075
1076 // Assemble the weak integral element
1077 return m_int_volume(idx_r, idx_theta)
1078 * (alpha
1079 * dot_product(
1080 basis_gradient_test_space,
1081 metric_tensor.to_covariant(basis_gradient_trial_space, coord))
1082 + beta * basis_val_test_space * basis_val_trial_space);
1083 }
1084
1089 template <class Mapping>
1090 double get_matrix_stencil_element(
1091 IdxBSRTheta idx_test,
1092 IdxBSRTheta idx_trial,
1093 host_t<ConstSpline2D> coeff_alpha,
1094 host_t<ConstSpline2D> coeff_beta,
1095 SplineRThetaEvaluatorNullBound const& evaluator,
1096 Mapping const& mapping)
1097 {
1098 // 0 <= idx_test_r < 8
1099 // 0 <= idx_trial_r < 8
1100 // idx_test_r < idx_trial_r
1101 const int idx_test_r(ddc::select<BSplinesR>(idx_test).uid());
1102 const int idx_trial_r(ddc::select<BSplinesR>(idx_trial).uid());
1103 // 0 <= idx_test_theta < 8
1104 // 0 <= idx_trial_theta < 8
1105 int idx_test_theta(theta_mod(ddc::select<BSplinesTheta>(idx_test).uid()));
1106 int idx_trial_theta(theta_mod(ddc::select<BSplinesTheta>(idx_trial).uid()));
1107
1108 const std::size_t ncells_r = ddc::discrete_space<BSplinesR>().ncells();
1109
1110 // 0<= r_offset <= degree_r
1111 // -degree_theta <= theta_offset <= degree_theta
1112 const int r_offset = idx_trial_r - idx_test_r;
1113 int theta_offset = theta_mod(idx_trial_theta - idx_test_theta);
1114 if (theta_offset >= int(m_nbasis_theta - BSplinesTheta::degree())) {
1115 theta_offset -= m_nbasis_theta;
1116 }
1117 assert(r_offset >= 0);
1118 assert(r_offset <= int(BSplinesR::degree()));
1119 assert(theta_offset >= -int(BSplinesTheta::degree()));
1120 assert(theta_offset <= int(BSplinesTheta::degree()));
1121
1122 // Find the index range covering the cells where both the test and trial functions are non-zero
1123 int n_overlap_stencil_r(BSplinesR::degree() + 1 - r_offset);
1124 int first_overlap_r(idx_trial_r - BSplinesR::degree());
1125
1126 int first_overlap_theta;
1127 int n_overlap_stencil_theta;
1128 if (theta_offset > 0) {
1129 n_overlap_stencil_theta = BSplinesTheta::degree() + 1 - theta_offset;
1130 first_overlap_theta = theta_mod(idx_trial_theta - BSplinesTheta::degree());
1131 } else {
1132 n_overlap_stencil_theta = BSplinesTheta::degree() + 1 + theta_offset;
1133 first_overlap_theta = theta_mod(idx_test_theta - BSplinesTheta::degree());
1134 }
1135
1136 if (first_overlap_r < 0) {
1137 const int n_compact = first_overlap_r;
1138 first_overlap_r = 0;
1139 n_overlap_stencil_r += n_compact;
1140 }
1141
1142 const int n_to_edge_r(ncells_r - first_overlap_r);
1143
1144 const IdxStep<RCellDim> n_overlap_r(min(n_overlap_stencil_r, n_to_edge_r));
1145 const IdxStep<ThetaCellDim> n_overlap_theta(n_overlap_stencil_theta);
1146
1147 const Idx<RCellDim> first_overlap_element_r(first_overlap_r);
1148 const Idx<ThetaCellDim> first_overlap_element_theta(first_overlap_theta);
1149
1150 const IdxRange<RCellDim> r_cells(first_overlap_element_r, n_overlap_r);
1151 const IdxRange<ThetaCellDim> theta_cells(first_overlap_element_theta, n_overlap_theta);
1152 const IdxRange<RCellDim, ThetaCellDim> non_zero_cells(r_cells, theta_cells);
1153
1154 assert(n_overlap_r * n_overlap_theta > 0);
1155 return ddc::transform_reduce(
1156 non_zero_cells,
1157 0.0,
1158 ddc::reducer::sum<double>(),
1159 [&](IdxCell const cell_idx) {
1160 const int cell_idx_r(ddc::select<RCellDim>(cell_idx).uid());
1161 const int cell_idx_theta(theta_mod(ddc::select<ThetaCellDim>(cell_idx).uid()));
1162
1163 const IdxRangeQuadratureRTheta cell_quad_points(
1164 get_quadrature_points_in_cell(cell_idx_r, cell_idx_theta));
1165
1166 int ib_test_theta_idx = idx_test_theta - cell_idx_theta;
1167 int ib_trial_theta_idx = idx_trial_theta - cell_idx_theta;
1168
1169 // Find the column where the non-zero data is stored
1170 Idx<RBasisSubset> ib_test_r(idx_test_r - cell_idx_r);
1171 Idx<ThetaBasisSubset> ib_test_theta(theta_mod(ib_test_theta_idx));
1172 Idx<RBasisSubset> ib_trial_r(idx_trial_r - cell_idx_r);
1173 Idx<ThetaBasisSubset> ib_trial_theta(theta_mod(ib_trial_theta_idx));
1174
1175 assert(ib_test_r.uid() < BSplinesR::degree() + 1);
1176 assert(ib_test_theta.uid() < BSplinesTheta::degree() + 1);
1177 assert(ib_trial_r.uid() < BSplinesR::degree() + 1);
1178 assert(ib_trial_theta.uid() < BSplinesTheta::degree() + 1);
1179
1180 // Calculate the weak integral
1181 return ddc::transform_reduce(
1182 cell_quad_points,
1183 0.0,
1184 ddc::reducer::sum<double>(),
1185 [&](IdxQuadratureRTheta const idx_quad) {
1186 IdxQuadratureR const idx_r = ddc::select<QDimRMesh>(idx_quad);
1187 IdxQuadratureTheta const idx_theta
1188 = ddc::select<QDimThetaMesh>(idx_quad);
1189 return weak_integral_element(
1190 idx_r,
1191 idx_theta,
1192 r_basis_vals_and_derivs(ib_test_r, idx_r),
1193 r_basis_vals_and_derivs(ib_trial_r, idx_r),
1194 m_theta_basis_vals_and_derivs(ib_test_theta, idx_theta),
1195 m_theta_basis_vals_and_derivs(ib_trial_theta, idx_theta),
1196 coeff_alpha,
1197 coeff_beta,
1198 evaluator,
1199 mapping);
1200 });
1201 });
1202 }
1203
1204 static KOKKOS_FUNCTION int theta_mod(int idx_theta)
1205 {
1206 int ncells_theta = ddc::discrete_space<BSplinesTheta>().ncells();
1207 while (idx_theta < 0)
1208 idx_theta += ncells_theta;
1209 while (idx_theta >= ncells_theta)
1210 idx_theta -= ncells_theta;
1211 return idx_theta;
1212 }
1213
1214public:
1228 void init_nnz_per_line(Kokkos::View<int*, Kokkos::LayoutRight> nnz) const
1229 {
1230 Kokkos::Profiling::pushRegion("PolarPoissonInitNnz");
1231 size_t const mat_size = nnz.extent(0) - 1;
1232 size_t constexpr n_singular_basis = PolarBSplinesRTheta::n_singular_basis();
1233 size_t constexpr degree = BSplinesR::degree();
1234 size_t constexpr radial_overlap = 2 * degree + 1;
1235 size_t const nbasis_theta_proxy = m_nbasis_theta;
1236
1237 // overlap between singular domain splines and radial splines
1238 Kokkos::parallel_for(
1239 "overlap singular radial",
1240 Kokkos::RangePolicy<Kokkos::DefaultExecutionSpace>(1, n_singular_basis + 1),
1241 KOKKOS_LAMBDA(const int k) {
1242 nnz(k + 1) = n_singular_basis + degree * nbasis_theta_proxy;
1243 });
1244
1245 // going from the internal boundary the overlapping possiblities between two radial splines increase
1246 Kokkos::parallel_for(
1247 "inner overlap",
1248 Kokkos::RangePolicy<Kokkos::DefaultExecutionSpace>(1, degree + 2),
1249 KOKKOS_LAMBDA(const int i) {
1250 for (size_t k = n_singular_basis + (i - 1) * nbasis_theta_proxy;
1251 k < n_singular_basis + i * nbasis_theta_proxy;
1252 k++) {
1253 nnz(k + 2) = n_singular_basis + (degree + i) * radial_overlap;
1254 }
1255 });
1256
1257 // Stencil with maximum possible overlap from two sides for radial spline
1258 Kokkos::parallel_for(
1259 "Inner Stencil",
1260 Kokkos::RangePolicy<Kokkos::DefaultExecutionSpace>(
1261 n_singular_basis + degree * nbasis_theta_proxy,
1262 mat_size - degree * nbasis_theta_proxy),
1263 KOKKOS_LAMBDA(const int k) { nnz(k + 2) = radial_overlap * radial_overlap; });
1264
1265 // Approaching the external boundary the overlapping possiblities between two radial splines decrease
1266 Kokkos::parallel_for(
1267 "outer overlap",
1268 Kokkos::RangePolicy<Kokkos::DefaultExecutionSpace>(1, degree + 1),
1269 KOKKOS_LAMBDA(const int i) {
1270 for (size_t k = mat_size - i * nbasis_theta_proxy;
1271 k < mat_size - (i - 1) * nbasis_theta_proxy;
1272 k++) {
1273 nnz(k + 2) = (degree + i) * radial_overlap;
1274 }
1275 });
1276
1277 // sum non-zero elements count
1278 Kokkos::parallel_for(
1279 "Sum over lines",
1280 Kokkos::RangePolicy<Kokkos::DefaultExecutionSpace>(0, 1),
1281 KOKKOS_LAMBDA(const int idx) {
1282 for (size_t k = 1; k < mat_size; k++) {
1283 nnz(k + 1) += nnz(k);
1284 }
1285 nnz(0) = 0;
1286 nnz(1) = 0;
1287 });
1288 Kokkos::Profiling::popRegion();
1289 }
1290};
Definition gauss_legendre_integration.hpp:27
Matrix class which is able to manage and solve a batch of sparse linear systems.
Definition matrix_batch_csr.hpp:36
An operator for calculating the metric tensor.
Definition metric_tensor.hpp:15
static KOKKOS_FUNCTION tensor_product_index_type get_2d_index(ddc::DiscreteElement< DDim > const &idx)
Get the 2D index of the tensor product bspline which, when evaluated at the same point,...
Definition polar_bsplines.hpp:153
BSplinesTheta BSplinesTheta_tag
The poloidal bspline from which the polar bsplines are constructed.
Definition polar_bsplines.hpp:50
static constexpr std::size_t n_singular_basis()
Get the number of singular bsplines i.e.
Definition polar_bsplines.hpp:103
static int constexpr continuity
The continuity enforced by the bsplines at the singular point.
Definition polar_bsplines.hpp:61
BSplinesR BSplinesR_tag
The radial bspline from which the polar bsplines are constructed.
Definition polar_bsplines.hpp:47
Define an evaluator on polar B-splines.
Definition polar_spline_evaluator.hpp:13
Define a polar PDE solver for a Poisson-like equation.
Definition polarpoissonlikesolver.hpp:50
void operator()(RHSFunction const &rhs, host_t< SplinePolar > &spline) const
Solve the Poisson-like equation.
Definition polarpoissonlikesolver.hpp:712
PolarSplineFEMPoissonLikeSolver(host_t< ConstSpline2D > coeff_alpha, host_t< ConstSpline2D > coeff_beta, Mapping const &mapping, SplineRThetaEvaluatorNullBound const &spline_evaluator)
Instantiate a polar Poisson-like solver using FEM with B-splines.
Definition polarpoissonlikesolver.hpp:268
void operator()(RHSFunction const &rhs, host_t< DFieldRTheta > phi) const
Solve the Poisson-like equation.
Definition polarpoissonlikesolver.hpp:872
void init_nnz_per_line(Kokkos::View< int *, Kokkos::LayoutRight > nnz) const
Fills the nnz data structure by computing the number of non-zero per line.
Definition polarpoissonlikesolver.hpp:1228
Definition polarpoissonlikesolver.hpp:64
Definition polarpoissonlikesolver.hpp:70
Definition polarpoissonlikesolver.hpp:67
Definition polarpoissonlikesolver.hpp:73
Definition geometry.hpp:93
Definition geometry.hpp:100
Definition geometry.hpp:116
Definition geometry.hpp:119
Definition geometry.hpp:103
Tag the first dimension for the quadrature mesh.
Definition polarpoissonlikesolver.hpp:82
Tag the second dimension for the quadrature mesh.
Definition polarpoissonlikesolver.hpp:88
A structure containing the two Chunks necessary to define a spline on a set of polar basis splines.
Definition polar_spline.hpp:20
Define non periodic real R dimension.
Definition geometry.hpp:31
Define periodic real Theta dimension.
Definition geometry.hpp:42